def test_simple(tctx): playbook, cff = start_h2_client(tctx) flow = Placeholder(HTTPFlow) server = Placeholder(Server) initial = Placeholder(bytes) assert (playbook >> DataReceived( tctx.client, cff.build_headers_frame(example_request_headers, flags=[ "END_STREAM" ]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >> reply(None, side_effect=make_h2) << SendData(server, initial)) frames = decode_frames(initial()) assert [type(x) for x in frames] == [ hyperframe.frame.SettingsFrame, hyperframe.frame.HeadersFrame, ] sff = FrameFactory() assert ( playbook # a conforming h2 server would send settings first, we disregard this for now. >> DataReceived( server, sff.build_headers_frame(example_response_headers).serialize()) << http.HttpResponseHeadersHook(flow) >> reply() >> DataReceived( server, sff.build_data_frame(b"Hello, World!", flags=["END_STREAM" ]).serialize()) << http.HttpResponseHook(flow) >> reply() << SendData( tctx.client, cff.build_headers_frame(example_response_headers).serialize() + cff.build_data_frame(b"Hello, World!").serialize() + cff.build_data_frame(b"", flags=["END_STREAM"]).serialize())) assert flow().request.url == "http://example.com/" assert flow().response.text == "Hello, World!"
def test_early_server_data(tctx): playbook, cff = start_h2_client(tctx) sff = FrameFactory() tctx.server.address = ("example.com", 80) tctx.server.state = ConnectionState.OPEN tctx.server.alpn = b"h2" flow = Placeholder(HTTPFlow) server1 = Placeholder(bytes) server2 = Placeholder(bytes) assert ( playbook >> DataReceived(tctx.client, cff.build_headers_frame(example_request_headers, flags=["END_STREAM"]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() << (h := http.HttpRequestHook(flow)) # Surprise! We get data from the server before the request hook finishes. >> DataReceived(tctx.server, sff.build_settings_frame({}).serialize()) << SendData(tctx.server, server1) # Request hook finishes... >> reply(to=h) << SendData(tctx.server, server2) ) assert [type(x) for x in decode_frames(server1())] == [ hyperframe.frame.SettingsFrame, hyperframe.frame.SettingsFrame, ] assert [type(x) for x in decode_frames(server2())] == [ hyperframe.frame.HeadersFrame, ]
def test_no_data_on_closed_stream(self, tctx): frame_factory = FrameFactory() req = Request.make("GET", "http://example.com/") resp = {":status": 200} assert ( Playbook(Http2Client(tctx)) << SendData( tctx.server, Placeholder(bytes)) # preamble + initial settings frame >> DataReceived( tctx.server, frame_factory.build_settings_frame({}, ack=True).serialize()) >> http.RequestHeaders(1, req, end_stream=True) << SendData( tctx.server, b"\x00\x00\x06\x01\x05\x00\x00\x00\x01\x82\x86\x84\\\x81\x07") >> http.RequestEndOfMessage(1) >> DataReceived( tctx.server, frame_factory.build_headers_frame(resp).serialize()) << http.ReceiveHttp(Placeholder( http.ResponseHeaders)) >> http.RequestProtocolError( 1, "cancelled", code=status_codes.CLIENT_CLOSED_REQUEST) << SendData( tctx.server, frame_factory.build_rst_stream_frame( 1, ErrorCodes.CANCEL).serialize()) >> DataReceived( tctx.server, frame_factory.build_data_frame(b"foo").serialize()) << SendData( tctx.server, frame_factory.build_rst_stream_frame( 1, ErrorCodes.STREAM_CLOSED).serialize()) ) # important: no ResponseData event here!
def start_h2_client(tctx: Context) -> Tuple[Playbook, FrameFactory]: tctx.client.alpn = b"h2" frame_factory = FrameFactory() playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) assert ( playbook << SendData(tctx.client, Placeholder()) # initial settings frame >> DataReceived(tctx.client, frame_factory.preamble()) >> DataReceived(tctx.client, frame_factory.build_settings_frame({}, ack=True).serialize()) ) return playbook, frame_factory
def test_response_trailers(tctx: Context, open_h2_server_conn: Server, stream): playbook, cff = start_h2_client(tctx) tctx.server = open_h2_server_conn sff = FrameFactory() def enable_streaming(flow: HTTPFlow): flow.response.stream = bool(stream) flow = Placeholder(HTTPFlow) ( playbook >> DataReceived(tctx.client, cff.build_headers_frame(example_request_headers, flags=["END_STREAM"]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply() << SendData(tctx.server, Placeholder(bytes)) # a conforming h2 server would send settings first, we disregard this for now. >> DataReceived(tctx.server, sff.build_headers_frame(example_response_headers).serialize() + sff.build_data_frame(b"Hello, World!").serialize()) << http.HttpResponseHeadersHook(flow) >> reply(side_effect=enable_streaming) ) if stream: playbook << SendData( tctx.client, cff.build_headers_frame(example_response_headers).serialize() + cff.build_data_frame(b"Hello, World!").serialize() ) assert ( playbook >> DataReceived(tctx.server, sff.build_headers_frame(example_response_trailers, flags=["END_STREAM"]).serialize()) << http.HttpResponseHook(flow) ) assert flow().response.trailers del flow().response.trailers["resp-trailer-a"] if stream: assert ( playbook >> reply() << SendData(tctx.client, cff.build_headers_frame(example_response_trailers[1:], flags=["END_STREAM"]).serialize()) ) else: assert ( playbook >> reply() << SendData(tctx.client, cff.build_headers_frame(example_response_headers).serialize() + cff.build_data_frame(b"Hello, World!").serialize() + cff.build_headers_frame(example_response_trailers[1:], flags=["END_STREAM"]).serialize()))
def test_max_concurrency(tctx): playbook, cff = start_h2_client(tctx) server = Placeholder(Server) req1_bytes = Placeholder(bytes) settings_ack_bytes = Placeholder(bytes) req2_bytes = Placeholder(bytes) playbook.hooks = False sff = FrameFactory() assert (playbook >> DataReceived( tctx.client, cff.build_headers_frame(example_request_headers, flags=["END_STREAM"], stream_id=1).serialize()) << OpenConnection(server) >> reply(None, side_effect=make_h2) << SendData(server, req1_bytes) >> DataReceived( server, sff.build_settings_frame({ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1 }).serialize()) << SendData( server, settings_ack_bytes) >> DataReceived( tctx.client, cff.build_headers_frame(example_request_headers, flags=["END_STREAM"], stream_id=3).serialize()) # Can't send it upstream yet, all streams in use! >> DataReceived( server, sff.build_headers_frame(example_response_headers, flags=["END_STREAM"], stream_id=1).serialize()) # But now we can! << SendData(server, req2_bytes) << SendData(tctx.client, Placeholder(bytes)) >> DataReceived( server, sff.build_headers_frame(example_response_headers, flags=["END_STREAM"], stream_id=3).serialize()) << SendData( tctx.client, Placeholder(bytes))) settings, req1 = decode_frames(req1_bytes()) settings_ack, = decode_frames(settings_ack_bytes()) req2, = decode_frames(req2_bytes()) assert type(settings) == hyperframe.frame.SettingsFrame assert type(req1) == hyperframe.frame.HeadersFrame assert type(settings_ack) == hyperframe.frame.SettingsFrame assert type(req2) == hyperframe.frame.HeadersFrame assert req1.stream_id == 1 assert req2.stream_id == 3
def test_no_normalization(tctx, normalize): """Test that we don't normalize headers when we just pass them through.""" tctx.options.normalize_outbound_headers = normalize tctx.options.validate_inbound_headers = False server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook, cff = start_h2_client(tctx) request_headers = list(example_request_headers) + [(b"Should-Not-Be-Capitalized! ", b" :) ")] request_headers_lower = [(k.lower(), v) for (k, v) in request_headers] response_headers = list(example_response_headers) + [(b"Same", b"Here")] response_headers_lower = [(k.lower(), v) for (k, v) in response_headers] initial = Placeholder(bytes) assert ( playbook >> DataReceived(tctx.client, cff.build_headers_frame(request_headers, flags=["END_STREAM"]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >> reply(None, side_effect=make_h2) << SendData(server, initial) ) frames = decode_frames(initial()) assert [type(x) for x in frames] == [ hyperframe.frame.SettingsFrame, hyperframe.frame.HeadersFrame, ] assert hpack.hpack.Decoder().decode(frames[1].data, True) == request_headers_lower if normalize else request_headers sff = FrameFactory() ( playbook >> DataReceived(server, sff.build_headers_frame(response_headers, flags=["END_STREAM"]).serialize()) << http.HttpResponseHeadersHook(flow) >> reply() << http.HttpResponseHook(flow) >> reply() ) if normalize: playbook << Log("Lowercased 'Same' header as uppercase is not allowed with HTTP/2.") hdrs = response_headers_lower if normalize else response_headers assert playbook << SendData(tctx.client, cff.build_headers_frame(hdrs, flags=["END_STREAM"]).serialize()) assert flow().request.headers.fields == ((b"Should-Not-Be-Capitalized! ", b" :) "),) assert flow().response.headers.fields == ((b"Same", b"Here"),)
def test_no_normalization(tctx): """Test that we don't normalize headers when we just pass them through.""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook, cff = start_h2_client(tctx) request_headers = example_request_headers + ( (b"Should-Not-Be-Capitalized! ", b" :) "), ) response_headers = example_response_headers + ( (b"Same", b"Here"), ) initial = Placeholder(bytes) assert ( playbook >> DataReceived(tctx.client, cff.build_headers_frame(request_headers, flags=["END_STREAM"]).serialize()) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply() << OpenConnection(server) >> reply(None, side_effect=make_h2) << SendData(server, initial) ) frames = decode_frames(initial()) assert [type(x) for x in frames] == [ hyperframe.frame.SettingsFrame, hyperframe.frame.HeadersFrame, ] assert hpack.hpack.Decoder().decode(frames[1].data, True) == list(request_headers) sff = FrameFactory() assert ( playbook >> DataReceived(server, sff.build_headers_frame(response_headers, flags=["END_STREAM"]).serialize()) << http.HttpResponseHeadersHook(flow) >> reply() << http.HttpResponseHook(flow) >> reply() << SendData(tctx.client, cff.build_headers_frame(response_headers).serialize() + cff.build_data_frame(b"", flags=["END_STREAM"]).serialize()) ) assert flow().request.headers.fields == ((b"Should-Not-Be-Capitalized! ", b" :) "),) assert flow().response.headers.fields == ((b"Same", b"Here"),)
def h2_frames(draw): ff = FrameFactory() headers1 = ff.build_headers_frame(headers=draw(h2_headers())) headers1.flags.clear() headers1.flags |= draw(h2_flags) headers2 = ff.build_headers_frame(headers=draw(h2_headers()), depends_on=draw(h2_stream_ids), stream_weight=draw(integers(0, 255)), exclusive=draw(booleans())) headers2.flags.clear() headers2.flags |= draw(h2_flags) settings = ff.build_settings_frame(settings=draw( dictionaries( keys=sampled_from(SettingCodes), values=integers(0, 2**32 - 1), max_size=5, )), ack=draw(booleans())) continuation = ff.build_continuation_frame(header_block=ff.encoder.encode( draw(h2_headers())), flags=draw(h2_flags)) goaway = ff.build_goaway_frame(draw(h2_stream_ids)) push_promise = ff.build_push_promise_frame( stream_id=draw(h2_stream_ids_nonzero), promised_stream_id=draw(h2_stream_ids), headers=draw(h2_headers())) rst = ff.build_rst_stream_frame(draw(h2_stream_ids_nonzero)) prio = ff.build_priority_frame( stream_id=draw(h2_stream_ids_nonzero), weight=draw(integers(0, 255)), depends_on=draw(h2_stream_ids), exclusive=draw(booleans()), ) data1 = ff.build_data_frame(draw(binary()), draw(h2_flags)) data2 = ff.build_data_frame(draw(binary()), draw(h2_flags), stream_id=draw(h2_stream_ids_nonzero)) window_update = ff.build_window_update_frame(draw(h2_stream_ids), draw(integers(0, 2**32 - 1))) frames = draw( lists(sampled_from([ headers1, headers2, settings, continuation, goaway, push_promise, rst, prio, data1, data2, window_update ]), min_size=1, max_size=11)) return b"".join(x.serialize() for x in frames)
import h2.config import h2.connection import h2.events from mitmproxy.http import HTTPFlow from mitmproxy.proxy.context import Context from mitmproxy.proxy.layers.http import HTTPMode from mitmproxy.proxy.commands import CloseConnection, OpenConnection, SendData from mitmproxy.connection import Server from mitmproxy.proxy.events import DataReceived from mitmproxy.proxy.layers import http from test.mitmproxy.proxy.layers.http.hyper_h2_test_helpers import FrameFactory from test.mitmproxy.proxy.layers.http.test_http2 import example_request_headers, example_response_headers, make_h2 from test.mitmproxy.proxy.tutils import Placeholder, Playbook, reply h2f = FrameFactory() def event_types(events): return [type(x) for x in events] def h2_client(tctx: Context) -> Tuple[h2.connection.H2Connection, Playbook]: tctx.client.alpn = b"h2" playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) conn = h2.connection.H2Connection() conn.initiate_connection() server_preamble = Placeholder(bytes) assert (playbook << SendData(tctx.client, server_preamble))