def test_read_headers_out_of_order(self): # If header blocks aren't decoded in the same order they're received, # regardless of the stream they belong to, the decoder state will # become corrupted. e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h1.flags |= set(['END_HEADERS', 'END_STREAM']) h3 = HeadersFrame(3) h3.data = e.encode([(':status', 200), ('content-type', 'baz/qux')]) h3.flags |= set(['END_HEADERS', 'END_STREAM']) sock = DummySocket() sock.buffer = BytesIO(h1.serialize() + h3.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock r1 = c.request('GET', '/a') r3 = c.request('GET', '/b') assert c.get_response(r3).headers == HTTPHeaderMap( [('content-type', 'baz/qux')] ) assert c.get_response(r1).headers == HTTPHeaderMap( [('content-type', 'foo/bar')] )
def test_read_headers_out_of_order(self): # If header blocks aren't decoded in the same order they're received, # regardless of the stream they belong to, the decoder state will # become corrupted. e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h1.flags |= set(['END_HEADERS', 'END_STREAM']) h3 = HeadersFrame(3) h3.data = e.encode([(':status', 200), ('content-type', 'baz/qux')]) h3.flags |= set(['END_HEADERS', 'END_STREAM']) sock = DummySocket() sock.buffer = BytesIO(h1.serialize() + h3.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock r1 = c.request('GET', '/a') r3 = c.request('GET', '/b') assert c.get_response(r3).headers == HTTPHeaderMap( [('content-type', 'baz/qux')] ) assert c.get_response(r1).headers == HTTPHeaderMap( [('content-type', 'foo/bar')] )
def test_stream_window_increments_appropriately(self, frame_buffer): e = Encoder() h = HeadersFrame(1) h.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h.flags = set(['END_HEADERS']) d = DataFrame(1) d.data = b'hi there sir' d2 = DataFrame(1) d2.data = b'hi there sir again' sock = DummySocket() sock.buffer = BytesIO(h.serialize() + d.serialize() + d2.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock c.request('GET', '/') c.streams[1]._in_window_manager.window_size = 1000 c.streams[1]._in_window_manager.initial_window_size = 1000 resp = c.get_response() resp.read(len(b'hi there sir')) resp.read(len(b'hi there sir again')) frame_buffer.add_data(b''.join(sock.queue)) queue = list(frame_buffer) assert len(queue) == 3 # one headers frame, two window update frames. assert isinstance(queue[1], WindowUpdateFrame) assert queue[1].window_increment == len(b'hi there sir') assert isinstance(queue[2], WindowUpdateFrame) assert queue[2].window_increment == len(b'hi there sir again')
def test_stream_window_increments_appropriately(self, frame_buffer): e = Encoder() h = HeadersFrame(1) h.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h.flags = set(['END_HEADERS']) d = DataFrame(1) d.data = b'hi there sir' d2 = DataFrame(1) d2.data = b'hi there sir again' sock = DummySocket() sock.buffer = BytesIO(h.serialize() + d.serialize() + d2.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock c.request('GET', '/') c.streams[1]._in_window_manager.window_size = 1000 c.streams[1]._in_window_manager.initial_window_size = 1000 resp = c.get_response() resp.read(len(b'hi there sir')) resp.read(len(b'hi there sir again')) frame_buffer.add_data(b''.join(sock.queue)) queue = list(frame_buffer) assert len(queue) == 3 # one headers frame, two window update frames. assert isinstance(queue[1], WindowUpdateFrame) assert queue[1].window_increment == len(b'hi there sir') assert isinstance(queue[2], WindowUpdateFrame) assert queue[2].window_increment == len(b'hi there sir again')
def test_incrementing_window_after_close(self): """ Hyper does not attempt to increment the flow control window once the stream is closed. """ # For this test, we want to send a response that has three frames at # the default max frame size (16,384 bytes). That will, on the third # frame, trigger the processing to increment the flow control window, # which should then not happen. f = SettingsFrame(0, settings={h2.settings.INITIAL_WINDOW_SIZE: 100}) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # Open stream 1. c.request('GET', '/') # Check what data we've sent right now. originally_sent_data = c._sock.queue[:] # Swap out the buffer to get a GoAway frame. length = 16384 total_length = (3 * 16384) + len(b'some more data') e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode( [(':status', 200), ('content-length', '%d' % total_length)] ) h1.flags |= set(['END_HEADERS']) d1 = DataFrame(1) d1.data = b'\x00' * length d2 = d1 d3 = d1 d4 = DataFrame(1) d4.data = b'some more data' d4.flags |= set(['END_STREAM']) buffer = BytesIO( b''.join(f.serialize() for f in [h1, d1, d2, d3, d4]) ) c._sock.buffer = buffer # Read the response resp = c.get_response(stream_id=1) assert resp.status == 200 assert resp.read() == b''.join( [b'\x00' * (3 * length), b'some more data'] ) # We should have sent only one extra frame assert len(originally_sent_data) + 1 == len(c._sock.queue)
def test_incrementing_window_after_close(self): """ Hyper does not attempt to increment the flow control window once the stream is closed. """ # For this test, we want to send a response that has three frames at # the default max frame size (16,384 bytes). That will, on the third # frame, trigger the processing to increment the flow control window, # which should then not happen. f = SettingsFrame(0, settings={h2.settings.INITIAL_WINDOW_SIZE: 100}) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # Open stream 1. c.request('GET', '/') # Check what data we've sent right now. originally_sent_data = c._sock.queue[:] # Swap out the buffer to get a GoAway frame. length = 16384 total_length = (3 * 16384) + len(b'some more data') e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode( [(':status', 200), ('content-length', '%d' % total_length)] ) h1.flags |= set(['END_HEADERS']) d1 = DataFrame(1) d1.data = b'\x00' * length d2 = d1 d3 = d1 d4 = DataFrame(1) d4.data = b'some more data' d4.flags |= set(['END_STREAM']) buffer = BytesIO( b''.join(f.serialize() for f in [h1, d1, d2, d3, d4]) ) c._sock.buffer = buffer # Read the response resp = c.get_response(stream_id=1) assert resp.status == 200 assert resp.read() == b''.join( [b'\x00' * (3 * length), b'some more data'] ) # We should have sent only one extra frame assert len(originally_sent_data) + 1 == len(c._sock.queue)
def slow_headers(self, conn, h2conn, method="GET"): LOGGER.info("SLOW HEADERS ATTACK=============================") h2conn.initiate_connection() wf = WindowUpdateFrame(0) wf.window_increment = WINDOW_SIZE_INCREMENT h2conn._data_to_send += wf.serialize() conn.sendall(h2conn.data_to_send()) headers = [(":authority", args.target), (":path", "/"), (":scheme", "http"), (":method", method)] hf = HeadersFrame(1) if method == "GET": hf.flags.add("END_STREAM") e = Encoder() hf.data = e.encode(headers) h2conn._data_to_send += hf.serialize() conn.sendall(h2conn.data_to_send())
def attack2(tls_conn, h2_conn): h2_conn.initiate_connection() wf = WindowUpdateFrame(0) wf.window_increment = WINDOW_INCREMENT_SIZE h2_conn._data_to_send += wf.serialize() tls_conn.sendall(h2_conn.data_to_send()) headers = [ (':authority', args.target), (':path', '/'), (':scheme', 'https'), (':method', 'POST'), ] hf = HeadersFrame(1) hf.flags.add('END_HEADERS') e = Encoder() hf.data = hf.data = e.encode(headers) h2_conn._data_to_send += hf.serialize() tls_conn.sendall(h2_conn.data_to_send())
def create_socket(status_code, data, headers): # test helper method encoder = Encoder() h1 = HeadersFrame(1) h1.data = encoder.encode([(':status', status_code), ('content-length', len(data))] + headers) h1.flags |= set(['END_HEADERS']) d1 = DataFrame(1) d1.data = data d2 = DataFrame(1) d2.flags |= set(['END_STREAM']) content = b''.join(f.serialize() for f in [h1, d1, d2]) buffer = BytesIO(content) return DummySocket(buffer)
def test_headers_with_continuation(self): e = Encoder() header_data = e.encode([(':status', 200), ('content-type', 'foo/bar'), ('content-length', '0')]) h = HeadersFrame(1) h.data = header_data[0:int(len(header_data) / 2)] h.flags.add('END_STREAM') c = ContinuationFrame(1) c.data = header_data[int(len(header_data) / 2):] c.flags.add('END_HEADERS') sock = DummySocket() sock.buffer = BytesIO(h.serialize() + c.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock r = c.request('GET', '/') assert set(c.get_response(r).headers.iter_raw()) == set([ (b'content-type', b'foo/bar'), (b'content-length', b'0') ])
def test_headers_with_continuation(self): e = Encoder() header_data = e.encode([ (':status', 200), ('content-type', 'foo/bar'), ('content-length', '0') ]) h = HeadersFrame(1) h.data = header_data[0:int(len(header_data)/2)] h.flags.add('END_STREAM') c = ContinuationFrame(1) c.data = header_data[int(len(header_data)/2):] c.flags.add('END_HEADERS') sock = DummySocket() sock.buffer = BytesIO(h.serialize() + c.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock r = c.request('GET', '/') assert set(c.get_response(r).headers.iter_raw()) == set( [(b'content-type', b'foo/bar'), (b'content-length', b'0')] )
def test_streams_removed_on_close(self): # Create content for read from socket e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h1.flags |= set(['END_HEADERS', 'END_STREAM']) sock = DummySocket() sock.buffer = BytesIO(h1.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock stream_id = c.request('GET', '/') # Create reference to current recent_recv_streams set recent_recv_streams = c.recent_recv_streams streams = c.streams resp = c.get_response(stream_id=stream_id) assert stream_id in recent_recv_streams assert stream_id in streams resp.read() assert stream_id not in recent_recv_streams assert stream_id not in streams
def test_streams_removed_on_close(self): # Create content for read from socket e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h1.flags |= set(['END_HEADERS', 'END_STREAM']) sock = DummySocket() sock.buffer = BytesIO(h1.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock stream_id = c.request('GET', '/') # Create reference to current recent_recv_streams set recent_recv_streams = c.recent_recv_streams streams = c.streams resp = c.get_response(stream_id=stream_id) assert stream_id in recent_recv_streams assert stream_id in streams resp.read() assert stream_id not in recent_recv_streams assert stream_id not in streams
class TestServerPush(object): def setup_method(self, method): self.frames = [] self.encoder = Encoder() self.conn = None def add_push_frame(self, stream_id, promised_stream_id, headers, end_block=True): frame = PushPromiseFrame(stream_id) frame.promised_stream_id = promised_stream_id frame.data = self.encoder.encode(headers) if end_block: frame.flags.add('END_HEADERS') self.frames.append(frame) def add_headers_frame(self, stream_id, headers, end_block=True, end_stream=False): frame = HeadersFrame(stream_id) frame.data = self.encoder.encode(headers) if end_block: frame.flags.add('END_HEADERS') if end_stream: frame.flags.add('END_STREAM') self.frames.append(frame) def add_data_frame(self, stream_id, data, end_stream=False): frame = DataFrame(stream_id) frame.data = data if end_stream: frame.flags.add('END_STREAM') self.frames.append(frame) def request(self): self.conn = HTTP20Connection('www.google.com', enable_push=True) self.conn._sock = DummySocket() self.conn._sock.buffer = BytesIO( b''.join([frame.serialize() for frame in self.frames]) ) self.conn.request('GET', '/') def assert_response(self): self.response = self.conn.get_response() assert self.response.status == 200 assert dict(self.response.headers) == {b'content-type': [b'text/html']} def assert_pushes(self): self.pushes = list(self.conn.get_pushes()) assert len(self.pushes) == 1 assert self.pushes[0].method == b'GET' assert self.pushes[0].scheme == b'https' assert self.pushes[0].authority == b'www.google.com' assert self.pushes[0].path == b'/' expected_headers = {b'accept-encoding': [b'gzip']} assert dict(self.pushes[0].request_headers) == expected_headers def assert_push_response(self): push_response = self.pushes[0].get_response() assert push_response.status == 200 assert dict(push_response.headers) == { b'content-type': [b'application/javascript'] } assert push_response.read() == b'bar' def test_promise_before_headers(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_data_frame(1, b'foo', end_stream=True) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(2, b'bar', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 self.assert_response() self.assert_pushes() assert self.response.read() == b'foo' self.assert_push_response() def test_promise_after_headers(self): self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_data_frame(1, b'foo', end_stream=True) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(2, b'bar', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 self.assert_response() assert self.response.read() == b'foo' self.assert_pushes() self.assert_push_response() def test_promise_after_data(self): self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_data_frame(1, b'fo') self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_data_frame(1, b'o', end_stream=True) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(2, b'bar', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 self.assert_response() assert self.response.read() == b'foo' self.assert_pushes() self.assert_push_response() def test_capture_all_promises(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/one'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_push_frame( 1, 4, [ (':method', 'GET'), (':path', '/two'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_data_frame(1, b'foo', end_stream=True) self.add_headers_frame( 4, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(4, b'two', end_stream=True) self.add_data_frame(2, b'one', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 pushes = list(self.conn.get_pushes(capture_all=True)) assert len(pushes) == 2 assert pushes[0].path == b'/one' assert pushes[1].path == b'/two' assert pushes[0].get_response().read() == b'one' assert pushes[1].get_response().read() == b'two' self.assert_response() assert self.response.read() == b'foo' def test_cancel_push(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.request() self.conn.get_response() list(self.conn.get_pushes())[0].cancel() f = RstStreamFrame(2) f.error_code = 8 assert self.conn._sock.queue[-1] == f.serialize() def test_reset_pushed_streams_when_push_disabled(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.request() self.conn._enable_push = False self.conn.get_response() f = RstStreamFrame(2) f.error_code = 7 assert self.conn._sock.queue[-1] == f.serialize() def test_pushed_requests_ignore_unexpected_headers(self): headers = HTTPHeaderMap([ (':scheme', 'http'), (':method', 'get'), (':authority', 'google.com'), (':path', '/'), (':reserved', 'no'), ('no', 'no'), ]) p = HTTP20Push(headers, DummyStream(b'')) assert p.request_headers == HTTPHeaderMap([('no', 'no')])
class TestServerPush(object): def setup_method(self, method): self.frames = [] self.encoder = Encoder() self.conn = None def add_push_frame(self, stream_id, promised_stream_id, headers, end_block=True): frame = PushPromiseFrame(stream_id) frame.promised_stream_id = promised_stream_id frame.data = self.encoder.encode(headers) if end_block: frame.flags.add('END_HEADERS') self.frames.append(frame) def add_headers_frame(self, stream_id, headers, end_block=True, end_stream=False): frame = HeadersFrame(stream_id) frame.data = self.encoder.encode(headers) if end_block: frame.flags.add('END_HEADERS') if end_stream: frame.flags.add('END_STREAM') self.frames.append(frame) def add_data_frame(self, stream_id, data, end_stream=False): frame = DataFrame(stream_id) frame.data = data if end_stream: frame.flags.add('END_STREAM') self.frames.append(frame) def request(self): self.conn = HTTP20Connection('www.google.com', enable_push=True) self.conn._sock = DummySocket() self.conn._sock.buffer = BytesIO( b''.join([frame.serialize() for frame in self.frames]) ) self.conn.request('GET', '/') def assert_response(self): self.response = self.conn.get_response() assert self.response.status == 200 assert dict(self.response.headers) == {b'content-type': [b'text/html']} def assert_pushes(self): self.pushes = list(self.conn.get_pushes()) assert len(self.pushes) == 1 assert self.pushes[0].method == b'GET' assert self.pushes[0].scheme == b'https' assert self.pushes[0].authority == b'www.google.com' assert self.pushes[0].path == b'/' expected_headers = {b'accept-encoding': [b'gzip']} assert dict(self.pushes[0].request_headers) == expected_headers def assert_push_response(self): push_response = self.pushes[0].get_response() assert push_response.status == 200 assert dict(push_response.headers) == { b'content-type': [b'application/javascript'] } assert push_response.read() == b'bar' def test_promise_before_headers(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_data_frame(1, b'foo', end_stream=True) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(2, b'bar', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 self.assert_response() self.assert_pushes() assert self.response.read() == b'foo' self.assert_push_response() def test_promise_after_headers(self): self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_data_frame(1, b'foo', end_stream=True) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(2, b'bar', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 self.assert_response() assert self.response.read() == b'foo' self.assert_pushes() self.assert_push_response() def test_promise_after_data(self): self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_data_frame(1, b'fo') self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_data_frame(1, b'o', end_stream=True) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(2, b'bar', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 self.assert_response() assert self.response.read() == b'foo' self.assert_pushes() self.assert_push_response() def test_capture_all_promises(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/one'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.add_push_frame( 1, 4, [ (':method', 'GET'), (':path', '/two'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_data_frame(1, b'foo', end_stream=True) self.add_headers_frame( 4, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_headers_frame( 2, [(':status', '200'), ('content-type', 'application/javascript')] ) self.add_data_frame(4, b'two', end_stream=True) self.add_data_frame(2, b'one', end_stream=True) self.request() assert len(list(self.conn.get_pushes())) == 0 pushes = list(self.conn.get_pushes(capture_all=True)) assert len(pushes) == 2 assert pushes[0].path == b'/one' assert pushes[1].path == b'/two' assert pushes[0].get_response().read() == b'one' assert pushes[1].get_response().read() == b'two' self.assert_response() assert self.response.read() == b'foo' def test_cancel_push(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.request() self.conn.get_response() list(self.conn.get_pushes())[0].cancel() f = RstStreamFrame(2) f.error_code = 8 assert self.conn._sock.queue[-1] == f.serialize() def test_reset_pushed_streams_when_push_disabled(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.request() self.conn._enable_push = False self.conn.get_response() f = RstStreamFrame(2) f.error_code = 7 assert self.conn._sock.queue[-1] == f.serialize() def test_pushed_requests_ignore_unexpected_headers(self): headers = HTTPHeaderMap([ (':scheme', 'http'), (':method', 'get'), (':authority', 'google.com'), (':path', '/'), (':reserved', 'no'), ('no', 'no'), ]) p = HTTP20Push(headers, DummyStream(b'')) assert p.request_headers == HTTPHeaderMap([('no', 'no')])