1# -*- coding: utf-8 -*-
2"""
3test_h2_upgrade.py
4~~~~~~~~~~~~~~~~~~
5
6This module contains tests that exercise the HTTP Upgrade functionality of
7hyper-h2, ensuring that clients and servers can upgrade their plaintext
8HTTP/1.1 connections to HTTP/2.
9"""
10import base64
11
12import pytest
13
14import h2.config
15import h2.connection
16import h2.errors
17import h2.events
18import h2.exceptions
19
20
21class TestClientUpgrade(object):
22    """
23    Tests of the client-side of the HTTP/2 upgrade dance.
24    """
25    example_request_headers = [
26        (b':authority', b'example.com'),
27        (b':path', b'/'),
28        (b':scheme', b'https'),
29        (b':method', b'GET'),
30    ]
31    example_response_headers = [
32        (b':status', b'200'),
33        (b'server', b'fake-serv/0.1.0')
34    ]
35
36    def test_returns_http2_settings(self, frame_factory):
37        """
38        Calling initiate_upgrade_connection returns a base64url encoded
39        Settings frame with the settings used by the connection.
40        """
41        conn = h2.connection.H2Connection()
42        data = conn.initiate_upgrade_connection()
43
44        # The base64 encoding must not be padded.
45        assert not data.endswith(b'=')
46
47        # However, SETTINGS frames should never need to be padded.
48        decoded_frame = base64.urlsafe_b64decode(data)
49        expected_frame = frame_factory.build_settings_frame(
50            settings=conn.local_settings
51        )
52        assert decoded_frame == expected_frame.serialize_body()
53
54    def test_emits_preamble(self, frame_factory):
55        """
56        Calling initiate_upgrade_connection emits the connection preamble.
57        """
58        conn = h2.connection.H2Connection()
59        conn.initiate_upgrade_connection()
60
61        data = conn.data_to_send()
62        assert data.startswith(frame_factory.preamble())
63
64        data = data[len(frame_factory.preamble()):]
65        expected_frame = frame_factory.build_settings_frame(
66            settings=conn.local_settings
67        )
68        assert data == expected_frame.serialize()
69
70    def test_can_receive_response(self, frame_factory):
71        """
72        After upgrading, we can safely receive a response.
73        """
74        c = h2.connection.H2Connection()
75        c.initiate_upgrade_connection()
76        c.clear_outbound_data_buffer()
77
78        f1 = frame_factory.build_headers_frame(
79            stream_id=1,
80            headers=self.example_response_headers,
81        )
82        f2 = frame_factory.build_data_frame(
83            stream_id=1,
84            data=b'some data',
85            flags=['END_STREAM']
86        )
87        events = c.receive_data(f1.serialize() + f2.serialize())
88        assert len(events) == 3
89
90        assert isinstance(events[0], h2.events.ResponseReceived)
91        assert isinstance(events[1], h2.events.DataReceived)
92        assert isinstance(events[2], h2.events.StreamEnded)
93
94        assert events[0].headers == self.example_response_headers
95        assert events[1].data == b'some data'
96        assert all(e.stream_id == 1 for e in events)
97
98        assert not c.data_to_send()
99
100    def test_can_receive_pushed_stream(self, frame_factory):
101        """
102        After upgrading, we can safely receive a pushed stream.
103        """
104        c = h2.connection.H2Connection()
105        c.initiate_upgrade_connection()
106        c.clear_outbound_data_buffer()
107
108        f = frame_factory.build_push_promise_frame(
109            stream_id=1,
110            promised_stream_id=2,
111            headers=self.example_request_headers,
112        )
113        events = c.receive_data(f.serialize())
114        assert len(events) == 1
115
116        assert isinstance(events[0], h2.events.PushedStreamReceived)
117        assert events[0].headers == self.example_request_headers
118        assert events[0].parent_stream_id == 1
119        assert events[0].pushed_stream_id == 2
120
121    def test_cannot_send_headers_stream_1(self, frame_factory):
122        """
123        After upgrading, we cannot send headers on stream 1.
124        """
125        c = h2.connection.H2Connection()
126        c.initiate_upgrade_connection()
127        c.clear_outbound_data_buffer()
128
129        with pytest.raises(h2.exceptions.ProtocolError):
130            c.send_headers(stream_id=1, headers=self.example_request_headers)
131
132    def test_cannot_send_data_stream_1(self, frame_factory):
133        """
134        After upgrading, we cannot send data on stream 1.
135        """
136        c = h2.connection.H2Connection()
137        c.initiate_upgrade_connection()
138        c.clear_outbound_data_buffer()
139
140        with pytest.raises(h2.exceptions.ProtocolError):
141            c.send_data(stream_id=1, data=b'some data')
142
143
144class TestServerUpgrade(object):
145    """
146    Tests of the server-side of the HTTP/2 upgrade dance.
147    """
148    example_request_headers = [
149        (b':authority', b'example.com'),
150        (b':path', b'/'),
151        (b':scheme', b'https'),
152        (b':method', b'GET'),
153    ]
154    example_response_headers = [
155        (b':status', b'200'),
156        (b'server', b'fake-serv/0.1.0')
157    ]
158    server_config = h2.config.H2Configuration(client_side=False)
159
160    def test_returns_nothing(self, frame_factory):
161        """
162        Calling initiate_upgrade_connection returns nothing.
163        """
164        conn = h2.connection.H2Connection(config=self.server_config)
165        curl_header = b"AAMAAABkAAQAAP__"
166        data = conn.initiate_upgrade_connection(curl_header)
167        assert data is None
168
169    def test_emits_preamble(self, frame_factory):
170        """
171        Calling initiate_upgrade_connection emits the connection preamble.
172        """
173        conn = h2.connection.H2Connection(config=self.server_config)
174        conn.initiate_upgrade_connection()
175
176        data = conn.data_to_send()
177        expected_frame = frame_factory.build_settings_frame(
178            settings=conn.local_settings
179        )
180        assert data == expected_frame.serialize()
181
182    def test_can_send_response(self, frame_factory):
183        """
184        After upgrading, we can safely send a response.
185        """
186        c = h2.connection.H2Connection(config=self.server_config)
187        c.initiate_upgrade_connection()
188        c.clear_outbound_data_buffer()
189
190        c.send_headers(stream_id=1, headers=self.example_response_headers)
191        c.send_data(stream_id=1, data=b'some data', end_stream=True)
192
193        f1 = frame_factory.build_headers_frame(
194            stream_id=1,
195            headers=self.example_response_headers,
196        )
197        f2 = frame_factory.build_data_frame(
198            stream_id=1,
199            data=b'some data',
200            flags=['END_STREAM']
201        )
202
203        expected_data = f1.serialize() + f2.serialize()
204        assert c.data_to_send() == expected_data
205
206    def test_can_push_stream(self, frame_factory):
207        """
208        After upgrading, we can safely push a stream.
209        """
210        c = h2.connection.H2Connection(config=self.server_config)
211        c.initiate_upgrade_connection()
212        c.clear_outbound_data_buffer()
213
214        c.push_stream(
215            stream_id=1,
216            promised_stream_id=2,
217            request_headers=self.example_request_headers
218        )
219
220        f = frame_factory.build_push_promise_frame(
221            stream_id=1,
222            promised_stream_id=2,
223            headers=self.example_request_headers,
224        )
225        assert c.data_to_send() == f.serialize()
226
227    def test_cannot_receive_headers_stream_1(self, frame_factory):
228        """
229        After upgrading, we cannot receive headers on stream 1.
230        """
231        c = h2.connection.H2Connection(config=self.server_config)
232        c.initiate_upgrade_connection()
233        c.receive_data(frame_factory.preamble())
234        c.clear_outbound_data_buffer()
235
236        f = frame_factory.build_headers_frame(
237            stream_id=1,
238            headers=self.example_request_headers,
239        )
240        c.receive_data(f.serialize())
241
242        expected_frame = frame_factory.build_rst_stream_frame(
243            stream_id=1,
244            error_code=h2.errors.ErrorCodes.STREAM_CLOSED,
245        )
246        assert c.data_to_send() == expected_frame.serialize()
247
248    def test_cannot_receive_data_stream_1(self, frame_factory):
249        """
250        After upgrading, we cannot receive data on stream 1.
251        """
252        c = h2.connection.H2Connection(config=self.server_config)
253        c.initiate_upgrade_connection()
254        c.receive_data(frame_factory.preamble())
255        c.clear_outbound_data_buffer()
256
257        f = frame_factory.build_data_frame(
258            stream_id=1,
259            data=b'some data',
260        )
261        c.receive_data(f.serialize())
262
263        expected = frame_factory.build_rst_stream_frame(
264            stream_id=1,
265            error_code=h2.errors.ErrorCodes.STREAM_CLOSED,
266        ).serialize()
267        assert c.data_to_send() == expected
268
269    def test_client_settings_are_applied(self, frame_factory):
270        """
271        The settings provided by the client are applied and immediately
272        ACK'ed.
273        """
274        server = h2.connection.H2Connection(config=self.server_config)
275        client = h2.connection.H2Connection()
276
277        # As a precaution, let's confirm that the server and client, at the
278        # start of the connection, do not agree on their initial settings
279        # state.
280        assert (
281            client.local_settings != server.remote_settings
282        )
283
284        # Get the client header data and pass it to the server.
285        header_data = client.initiate_upgrade_connection()
286        server.initiate_upgrade_connection(header_data)
287
288        # This gets complex, but here we go.
289        # RFC 7540 § 3.2.1 says that "explicit acknowledgement" of the settings
290        # in the header is "not necessary". That's annoyingly vague, but we
291        # interpret that to mean "should not be sent". So to test that this
292        # worked we need to test that the server has only sent the preamble,
293        # and has not sent a SETTINGS ack, and also that the server has the
294        # correct settings.
295        expected_frame = frame_factory.build_settings_frame(
296            server.local_settings
297        )
298        assert server.data_to_send() == expected_frame.serialize()
299
300        assert (
301            client.local_settings == server.remote_settings
302        )
303