1# -*- coding: utf-8 -*-
2"""
3test_informational_responses
4~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6Tests that validate that hyper-h2 correctly handles informational (1XX)
7responses in its state machine.
8"""
9import pytest
10
11import h2.config
12import h2.connection
13import h2.events
14import h2.exceptions
15
16
17class TestReceivingInformationalResponses(object):
18    """
19    Tests for receiving informational responses.
20    """
21    example_request_headers = [
22        (b':authority', b'example.com'),
23        (b':path', b'/'),
24        (b':scheme', b'https'),
25        (b':method', b'GET'),
26        (b'expect', b'100-continue'),
27    ]
28    example_informational_headers = [
29        (b':status', b'100'),
30        (b'server', b'fake-serv/0.1.0')
31    ]
32    example_response_headers = [
33        (b':status', b'200'),
34        (b'server', b'fake-serv/0.1.0')
35    ]
36    example_trailers = [
37        (b'trailer', b'you-bet'),
38    ]
39
40    @pytest.mark.parametrize('end_stream', (True, False))
41    def test_single_informational_response(self, frame_factory, end_stream):
42        """
43        When receiving a informational response, the appropriate event is
44        signaled.
45        """
46        c = h2.connection.H2Connection()
47        c.initiate_connection()
48        c.send_headers(
49            stream_id=1,
50            headers=self.example_request_headers,
51            end_stream=end_stream
52        )
53
54        f = frame_factory.build_headers_frame(
55            headers=self.example_informational_headers,
56            stream_id=1,
57        )
58        events = c.receive_data(f.serialize())
59
60        assert len(events) == 1
61        event = events[0]
62
63        assert isinstance(event, h2.events.InformationalResponseReceived)
64        assert event.headers == self.example_informational_headers
65        assert event.stream_id == 1
66
67    @pytest.mark.parametrize('end_stream', (True, False))
68    def test_receiving_multiple_header_blocks(self, frame_factory, end_stream):
69        """
70        At least three header blocks can be received: informational, headers,
71        trailers.
72        """
73        c = h2.connection.H2Connection()
74        c.initiate_connection()
75        c.send_headers(
76            stream_id=1,
77            headers=self.example_request_headers,
78            end_stream=end_stream
79        )
80
81        f1 = frame_factory.build_headers_frame(
82            headers=self.example_informational_headers,
83            stream_id=1,
84        )
85        f2 = frame_factory.build_headers_frame(
86            headers=self.example_response_headers,
87            stream_id=1,
88        )
89        f3 = frame_factory.build_headers_frame(
90            headers=self.example_trailers,
91            stream_id=1,
92            flags=['END_STREAM'],
93        )
94        events = c.receive_data(
95            f1.serialize() + f2.serialize() + f3.serialize()
96        )
97
98        assert len(events) == 4
99
100        assert isinstance(events[0], h2.events.InformationalResponseReceived)
101        assert events[0].headers == self.example_informational_headers
102        assert events[0].stream_id == 1
103
104        assert isinstance(events[1], h2.events.ResponseReceived)
105        assert events[1].headers == self.example_response_headers
106        assert events[1].stream_id == 1
107
108        assert isinstance(events[2], h2.events.TrailersReceived)
109        assert events[2].headers == self.example_trailers
110        assert events[2].stream_id == 1
111
112    @pytest.mark.parametrize('end_stream', (True, False))
113    def test_receiving_multiple_informational_responses(self,
114                                                        frame_factory,
115                                                        end_stream):
116        """
117        More than one informational response is allowed.
118        """
119        c = h2.connection.H2Connection()
120        c.initiate_connection()
121        c.send_headers(
122            stream_id=1,
123            headers=self.example_request_headers,
124            end_stream=end_stream
125        )
126
127        f1 = frame_factory.build_headers_frame(
128            headers=self.example_informational_headers,
129            stream_id=1,
130        )
131        f2 = frame_factory.build_headers_frame(
132            headers=[(':status', '101')],
133            stream_id=1,
134        )
135        events = c.receive_data(f1.serialize() + f2.serialize())
136
137        assert len(events) == 2
138
139        assert isinstance(events[0], h2.events.InformationalResponseReceived)
140        assert events[0].headers == self.example_informational_headers
141        assert events[0].stream_id == 1
142
143        assert isinstance(events[1], h2.events.InformationalResponseReceived)
144        assert events[1].headers == [(b':status', b'101')]
145        assert events[1].stream_id == 1
146
147    @pytest.mark.parametrize('end_stream', (True, False))
148    def test_receive_provisional_response_with_end_stream(self,
149                                                          frame_factory,
150                                                          end_stream):
151        """
152        Receiving provisional responses with END_STREAM set causes
153        ProtocolErrors.
154        """
155        c = h2.connection.H2Connection()
156        c.initiate_connection()
157        c.send_headers(
158            stream_id=1,
159            headers=self.example_request_headers,
160            end_stream=end_stream
161        )
162        c.clear_outbound_data_buffer()
163
164        f = frame_factory.build_headers_frame(
165            headers=self.example_informational_headers,
166            stream_id=1,
167            flags=['END_STREAM']
168        )
169
170        with pytest.raises(h2.exceptions.ProtocolError):
171            c.receive_data(f.serialize())
172
173        expected = frame_factory.build_goaway_frame(
174            last_stream_id=0,
175            error_code=1,
176        )
177        assert c.data_to_send() == expected.serialize()
178
179    @pytest.mark.parametrize('end_stream', (True, False))
180    def test_receiving_out_of_order_headers(self, frame_factory, end_stream):
181        """
182        When receiving a informational response after the actual response
183        headers we consider it a ProtocolError and raise it.
184        """
185        c = h2.connection.H2Connection()
186        c.initiate_connection()
187        c.send_headers(
188            stream_id=1,
189            headers=self.example_request_headers,
190            end_stream=end_stream
191        )
192
193        f1 = frame_factory.build_headers_frame(
194            headers=self.example_response_headers,
195            stream_id=1,
196        )
197        f2 = frame_factory.build_headers_frame(
198            headers=self.example_informational_headers,
199            stream_id=1,
200        )
201        c.receive_data(f1.serialize())
202        c.clear_outbound_data_buffer()
203
204        with pytest.raises(h2.exceptions.ProtocolError):
205            c.receive_data(f2.serialize())
206
207        expected = frame_factory.build_goaway_frame(
208            last_stream_id=0,
209            error_code=1,
210        )
211        assert c.data_to_send() == expected.serialize()
212
213
214class TestSendingInformationalResponses(object):
215    """
216    Tests for sending informational responses.
217    """
218    example_request_headers = [
219        (b':authority', b'example.com'),
220        (b':path', b'/'),
221        (b':scheme', b'https'),
222        (b':method', b'GET'),
223        (b'expect', b'100-continue'),
224    ]
225    unicode_informational_headers = [
226        (u':status', u'100'),
227        (u'server', u'fake-serv/0.1.0')
228    ]
229    bytes_informational_headers = [
230        (b':status', b'100'),
231        (b'server', b'fake-serv/0.1.0')
232    ]
233    example_response_headers = [
234        (b':status', b'200'),
235        (b'server', b'fake-serv/0.1.0')
236    ]
237    example_trailers = [
238        (b'trailer', b'you-bet'),
239    ]
240    server_config = h2.config.H2Configuration(client_side=False)
241
242    @pytest.mark.parametrize(
243        'hdrs', (unicode_informational_headers, bytes_informational_headers),
244    )
245    @pytest.mark.parametrize('end_stream', (True, False))
246    def test_single_informational_response(self,
247                                           frame_factory,
248                                           hdrs,
249                                           end_stream):
250        """
251        When sending a informational response, the appropriate frames are
252        emitted.
253        """
254        c = h2.connection.H2Connection(config=self.server_config)
255        c.initiate_connection()
256        c.receive_data(frame_factory.preamble())
257        flags = ['END_STREAM'] if end_stream else []
258        f = frame_factory.build_headers_frame(
259            headers=self.example_request_headers,
260            stream_id=1,
261            flags=flags,
262        )
263        c.receive_data(f.serialize())
264        c.clear_outbound_data_buffer()
265        frame_factory.refresh_encoder()
266
267        c.send_headers(
268            stream_id=1,
269            headers=hdrs
270        )
271
272        f = frame_factory.build_headers_frame(
273            headers=hdrs,
274            stream_id=1,
275        )
276        assert c.data_to_send() == f.serialize()
277
278    @pytest.mark.parametrize(
279        'hdrs', (unicode_informational_headers, bytes_informational_headers),
280    )
281    @pytest.mark.parametrize('end_stream', (True, False))
282    def test_sending_multiple_header_blocks(self,
283                                            frame_factory,
284                                            hdrs,
285                                            end_stream):
286        """
287        At least three header blocks can be sent: informational, headers,
288        trailers.
289        """
290        c = h2.connection.H2Connection(config=self.server_config)
291        c.initiate_connection()
292        c.receive_data(frame_factory.preamble())
293        flags = ['END_STREAM'] if end_stream else []
294        f = frame_factory.build_headers_frame(
295            headers=self.example_request_headers,
296            stream_id=1,
297            flags=flags,
298        )
299        c.receive_data(f.serialize())
300        c.clear_outbound_data_buffer()
301        frame_factory.refresh_encoder()
302
303        # Send the three header blocks.
304        c.send_headers(
305            stream_id=1,
306            headers=hdrs
307        )
308        c.send_headers(
309            stream_id=1,
310            headers=self.example_response_headers
311        )
312        c.send_headers(
313            stream_id=1,
314            headers=self.example_trailers,
315            end_stream=True
316        )
317
318        # Check that we sent them properly.
319        f1 = frame_factory.build_headers_frame(
320            headers=hdrs,
321            stream_id=1,
322        )
323        f2 = frame_factory.build_headers_frame(
324            headers=self.example_response_headers,
325            stream_id=1,
326        )
327        f3 = frame_factory.build_headers_frame(
328            headers=self.example_trailers,
329            stream_id=1,
330            flags=['END_STREAM']
331        )
332        assert (
333            c.data_to_send() ==
334            f1.serialize() + f2.serialize() + f3.serialize()
335        )
336
337    @pytest.mark.parametrize(
338        'hdrs', (unicode_informational_headers, bytes_informational_headers),
339    )
340    @pytest.mark.parametrize('end_stream', (True, False))
341    def test_sending_multiple_informational_responses(self,
342                                                      frame_factory,
343                                                      hdrs,
344                                                      end_stream):
345        """
346        More than one informational response is allowed.
347        """
348        c = h2.connection.H2Connection(config=self.server_config)
349        c.initiate_connection()
350        c.receive_data(frame_factory.preamble())
351        flags = ['END_STREAM'] if end_stream else []
352        f = frame_factory.build_headers_frame(
353            headers=self.example_request_headers,
354            stream_id=1,
355            flags=flags,
356        )
357        c.receive_data(f.serialize())
358        c.clear_outbound_data_buffer()
359        frame_factory.refresh_encoder()
360
361        # Send two informational responses.
362        c.send_headers(
363            stream_id=1,
364            headers=hdrs,
365        )
366        c.send_headers(
367            stream_id=1,
368            headers=[(':status', '101')]
369        )
370
371        # Check we sent them both.
372        f1 = frame_factory.build_headers_frame(
373            headers=hdrs,
374            stream_id=1,
375        )
376        f2 = frame_factory.build_headers_frame(
377            headers=[(':status', '101')],
378            stream_id=1,
379        )
380        assert c.data_to_send() == f1.serialize() + f2.serialize()
381
382    @pytest.mark.parametrize(
383        'hdrs', (unicode_informational_headers, bytes_informational_headers),
384    )
385    @pytest.mark.parametrize('end_stream', (True, False))
386    def test_send_provisional_response_with_end_stream(self,
387                                                       frame_factory,
388                                                       hdrs,
389                                                       end_stream):
390        """
391        Sending provisional responses with END_STREAM set causes
392        ProtocolErrors.
393        """
394        c = h2.connection.H2Connection(config=self.server_config)
395        c.initiate_connection()
396        c.receive_data(frame_factory.preamble())
397        flags = ['END_STREAM'] if end_stream else []
398        f = frame_factory.build_headers_frame(
399            headers=self.example_request_headers,
400            stream_id=1,
401            flags=flags,
402        )
403        c.receive_data(f.serialize())
404
405        with pytest.raises(h2.exceptions.ProtocolError):
406            c.send_headers(
407                stream_id=1,
408                headers=hdrs,
409                end_stream=True,
410            )
411
412    @pytest.mark.parametrize(
413        'hdrs', (unicode_informational_headers, bytes_informational_headers),
414    )
415    @pytest.mark.parametrize('end_stream', (True, False))
416    def test_reject_sending_out_of_order_headers(self,
417                                                 frame_factory,
418                                                 hdrs,
419                                                 end_stream):
420        """
421        When sending an informational response after the actual response
422        headers we consider it a ProtocolError and raise it.
423        """
424        c = h2.connection.H2Connection(config=self.server_config)
425        c.initiate_connection()
426        c.receive_data(frame_factory.preamble())
427        flags = ['END_STREAM'] if end_stream else []
428        f = frame_factory.build_headers_frame(
429            headers=self.example_request_headers,
430            stream_id=1,
431            flags=flags,
432        )
433        c.receive_data(f.serialize())
434
435        c.send_headers(
436            stream_id=1,
437            headers=self.example_response_headers
438        )
439
440        with pytest.raises(h2.exceptions.ProtocolError):
441            c.send_headers(
442                stream_id=1,
443                headers=hdrs
444            )
445