def test_read_frame_failed(): raw = codecs.decode('485454000000000000', 'hex_codec') bio = BytesIO(raw) bio.safe_read = bio.read with pytest.raises(exceptions.HttpException): _ = http2.read_frame(bio, False)
def test_connection_lost(self): h2_conn = self.setup_connection() self._send_request(self.client.wfile, h2_conn, stream_id=1, headers=[(':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ('foo', 'bar')]) done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False except: break try: self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() except: break if len(self.master.state.flows) == 1: assert self.master.state.flows[0].response is None
def test_response_streaming(self, streaming): class Stream: def responseheaders(self, f): f.response.stream = streaming self.master.addons.add(Stream()) h2_conn = self.setup_connection() self._send_request(self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ]) done = False self.client.rfile.o.settimeout(2) data = None while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) for event in events: if isinstance(event, h2.events.DataReceived): data = event.data done = True except: break if streaming: assert data else: assert data is None
def test_connection_terminated(self): h2_conn = self.setup_connection() self._send_request(self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ]) done = False connection_terminated_event = None while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) for event in events: if isinstance(event, h2.events.ConnectionTerminated): connection_terminated_event = event done = True except: break assert len(self.master.state.flows) == 1 assert connection_terminated_event is not None assert connection_terminated_event.error_code == 5 assert connection_terminated_event.last_stream_id == 42 assert connection_terminated_event.additional_data == b'foobar'
def test_priority(self, prioritize_before, http2_priority_enabled, priority, expected_priority): self.options.http2_priority = http2_priority_enabled self.__class__.priority_data = [] h2_conn = self.setup_connection() if prioritize_before: h2_conn.prioritize(1, exclusive=priority[0], depends_on=priority[1], weight=priority[2]) self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() self._send_request( self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ], end_stream=prioritize_before, ) if not prioritize_before: h2_conn.prioritize(1, exclusive=priority[0], depends_on=priority[1], weight=priority[2]) h2_conn.end_stream(1) self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamEnded): done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == 1 assert self.priority_data == expected_priority
def test_push_promise(self): h2_conn = self.setup_connection() self._send_request(self.client.wfile, h2_conn, stream_id=1, headers=[(':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ('foo', 'bar')]) done = False ended_streams = 0 pushed_streams = 0 responses = 0 while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False except: break self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamEnded): ended_streams += 1 elif isinstance(event, h2.events.PushedStreamReceived): pushed_streams += 1 elif isinstance(event, h2.events.ResponseReceived): responses += 1 if isinstance(event, h2.events.ConnectionTerminated): done = True if responses == 3 and ended_streams == 3 and pushed_streams == 2: done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert ended_streams == 3 assert pushed_streams == 2 bodies = [flow.response.content for flow in self.master.state.flows] assert len(bodies) == 3 assert b'regular_stream' in bodies assert b'pushed_stream_foo' in bodies assert b'pushed_stream_bar' in bodies pushed_flows = [ flow for flow in self.master.state.flows if 'h2-pushed-stream' in flow.metadata ] assert len(pushed_flows) == 2
def test_read_frame(): raw = codecs.decode('000006000101234567666f6f626172', 'hex_codec') bio = BytesIO(raw) bio.safe_read = bio.read frame, consumed_bytes = http2.read_frame(bio) assert isinstance(frame, hyperframe.frame.DataFrame) assert frame.stream_id == 19088743 assert 'END_STREAM' in frame.flags assert len(frame.flags) == 1 assert frame.data == b'foobar' assert consumed_bytes == raw bio = BytesIO(raw) bio.safe_read = bio.read frame, consumed_bytes = http2.read_frame(bio, False) assert frame is None assert consumed_bytes == raw
def handle(self): # send magic self.wfile.write(bytes.fromhex("505249202a20485454502f322e300d0a0d0a534d0d0a0d0a")) self.wfile.flush() # send empty settings frame self.wfile.write(bytes.fromhex("000000040000000000")) self.wfile.flush() # check empty settings frame _, consumed_bytes = http2.read_frame(self.rfile, False) assert consumed_bytes == bytes.fromhex("00000c040000000000000200000000000300000001") # check settings acknowledgement _, consumed_bytes = http2.read_frame(self.rfile, False) assert consumed_bytes == bytes.fromhex("000000040100000000") # send settings acknowledgement self.wfile.write(bytes.fromhex("000000040100000000")) self.wfile.flush()
def test_trailers(self, announce, body): h2_conn = self.setup_connection() stream_id = 1 headers = [ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ] if announce: headers.append(('trailer', 'x-my-trailers')) h2_conn.send_headers( stream_id=stream_id, headers=headers, ) if body: h2_conn.send_data(stream_id, body) # send trailers h2_conn.send_headers(stream_id, [('x-my-trailers', 'foobar')], end_stream=True) self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamEnded): done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == 1 assert self.master.state.flows[0].request.trailers[ 'x-my-trailers'] == 'foobar' assert self.master.state.flows[0].response.status_code == 200 assert self.master.state.flows[0].response.headers[ 'x-my-trailer-request-received'] == 'success'
def test_request_with_priority(self, http2_priority_enabled, priority, expected_priority): self.options.http2_priority = http2_priority_enabled h2_conn = self.setup_connection() self._send_request( self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ], priority_exclusive=priority[0], priority_depends_on=priority[1], priority_weight=priority[2], ) done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamEnded): done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == 1 resp = self.master.state.flows[0].response assert resp.headers.get('priority_exclusive', None) == expected_priority[0] assert resp.headers.get('priority_depends_on', None) == expected_priority[1] assert resp.headers.get('priority_weight', None) == expected_priority[2]
def test_simple_request_with_body(self): response_body_buffer = b'' h2_conn = self.setup_connection() self._send_request(self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ('self.client-FoO', 'self.client-bar-1'), ('self.client-FoO', 'self.client-bar-2'), ], body=b'request body') done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.DataReceived): response_body_buffer += event.data elif isinstance(event, h2.events.StreamEnded): done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == 1 assert self.master.state.flows[0].response.status_code == 200 assert self.master.state.flows[0].response.headers[ 'server-foo'] == 'server-bar' assert self.master.state.flows[0].response.headers['föo'] == 'bär' assert self.master.state.flows[0].response.content == b'response body' assert self.request_body_buffer == b'request body' assert response_body_buffer == b'response body'
def test_trailers(self, announce): response_body_buffer = b'' h2_conn = self.setup_connection() self._send_request(self.client.wfile, h2_conn, stream_id=(1 if announce else 3), headers=[ (':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ]) trailers_buffer = None done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.DataReceived): response_body_buffer += event.data elif isinstance(event, h2.events.TrailersReceived): trailers_buffer = event.headers elif isinstance(event, h2.events.StreamEnded): done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == 1 assert self.master.state.flows[0].response.status_code == 200 assert self.master.state.flows[0].response.content == b'response body' assert response_body_buffer == b'response body' assert self.master.state.flows[0].response.trailers[ 'x-my-trailers'] == 'foobar' assert trailers_buffer == [(b'x-my-trailers', b'foobar')]
def __call__(self): self._initiate_server_conn() self._complete_handshake() conns = [c.connection for c in self.connections.keys()] try: while True: r = tcp.ssl_read_select(conns, 0.1) for conn in r: source_conn = self.client_conn if conn == self.client_conn.connection else self.server_conn other_conn = self.server_conn if conn == self.client_conn.connection else self.client_conn is_server = (source_conn == self.server_conn) with self.connections[source_conn].lock: try: _, consumed_bytes = http2.read_frame( source_conn.rfile) except: # read frame failed: connection closed self._kill_all_streams() return if self.connections[ source_conn].state_machine.state == h2.connection.ConnectionState.CLOSED: self.log( "HTTP/2 connection entered closed state already", "debug") return incoming_events = self.connections[ source_conn].receive_data(consumed_bytes) source_conn.send( self.connections[source_conn].data_to_send()) for event in incoming_events: if not self._handle_event(event, source_conn, other_conn, is_server): # connection terminated: GoAway self._kill_all_streams() return self._cleanup_streams() except Exception as e: # pragma: no cover self.log(repr(e), "info") self._kill_all_streams()
def read_frame(self, hide=False): while True: frm, _ = http2.read_frame(self.tcp_handler.rfile) if not hide and self.dump_frames: # pragma: no cover print("<< " + repr(frm)) if isinstance(frm, hyperframe.frame.PingFrame): raw_bytes = hyperframe.frame.PingFrame(flags=['ACK'], payload=frm.payload).serialize() self.tcp_handler.wfile.write(raw_bytes) self.tcp_handler.wfile.flush() continue if isinstance(frm, hyperframe.frame.SettingsFrame) and 'ACK' not in frm.flags: self._apply_settings(frm.settings, hide) if isinstance(frm, hyperframe.frame.DataFrame) and frm.flow_controlled_length > 0: self._update_flow_control_window(frm.stream_id, frm.flow_controlled_length) return frm
def handle(self): config = h2.config.H2Configuration(client_side=False, validate_outbound_headers=False, validate_inbound_headers=False) h2_conn = h2.connection.H2Connection(config) preamble = self.rfile.read(24) h2_conn.initiate_connection() h2_conn.receive_data(preamble) self.wfile.write(h2_conn.data_to_send()) self.wfile.flush() if 'h2_server_settings' in self.kwargs: h2_conn.update_settings(self.kwargs['h2_server_settings']) self.wfile.write(h2_conn.data_to_send()) self.wfile.flush() done = False while not done: try: _, consumed_bytes = http2.read_frame(self.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False except exceptions.TcpDisconnect: break except: print(traceback.format_exc()) break self.wfile.write(h2_conn.data_to_send()) self.wfile.flush() for event in events: try: if not self.server.handle_server_event( event, h2_conn, self.rfile, self.wfile): done = True break except exceptions.TcpDisconnect: done = True except: done = True print(traceback.format_exc()) break
def test_request_streaming(self, streaming): class Stream: def requestheaders(self, f): f.request.stream = streaming self.master.addons.add(Stream()) h2_conn = self.setup_connection() body = generators.RandomGenerator("bytes", 100)[:] self._send_request(self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ], body=body, streaming=True) done = False connection_terminated_event = None self.client.rfile.o.settimeout(2) while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) for event in events: if isinstance(event, h2.events.ConnectionTerminated): connection_terminated_event = event done = True except mitmproxy.exceptions.TcpTimeout: if not streaming: break # this is expected for this test case else: assert False except: print(traceback.format_exc()) assert False if streaming: assert connection_terminated_event.additional_data == body else: assert connection_terminated_event is None
def test_max_concurrent_streams(self): h2_conn = self.setup_connection() new_streams = [1, 3, 5, 7, 9, 11] for stream_id in new_streams: # this will exceed MAX_CONCURRENT_STREAMS on the server connection # and cause mitmproxy to throttle stream creation to the server self._send_request(self.client.wfile, h2_conn, stream_id=stream_id, headers=[ (':authority', "127.0.0.1:{}".format( self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ('X-Stream-ID', str(stream_id)), ]) ended_streams = 0 while ended_streams != len(new_streams): try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except: break self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamEnded): ended_streams += 1 h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == len(new_streams) for flow in self.master.state.flows: assert flow.response.status_code == 200 assert b"Stream-ID " in flow.response.content
def test_body_size_limit(self): self.options.body_size_limit = "20" h2_conn = self.setup_connection() self._send_request( self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ], body=b'very long body over 20 characters long', ) done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamReset): done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() assert len(self.master.state.flows) == 0
def run_test_for_stream_reset(self): h2_conn = self.setup_connection() self._send_request( self.client.wfile, h2_conn, headers=[ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ], ) self.client.rfile.o.settimeout(1) done = False while not done: try: _, consumed_bytes = http2.read_frame(self.client.rfile, False) events = h2_conn.receive_data(consumed_bytes) except exceptions.HttpException: print(traceback.format_exc()) assert False self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush() for event in events: if isinstance(event, h2.events.StreamReset): assert event.error_code == int(self.current_error_code) done = True h2_conn.close_connection() self.client.wfile.write(h2_conn.data_to_send()) self.client.wfile.flush()