1# -*- coding: utf-8 -*-
2"""
3test_invalid_headers.py
4~~~~~~~~~~~~~~~~~~~~~~~
5
6This module contains tests that use invalid header blocks, and validates that
7they fail appropriately.
8"""
9import itertools
10
11import pytest
12
13import h2.config
14import h2.connection
15import h2.errors
16import h2.events
17import h2.exceptions
18import h2.settings
19import h2.utilities
20
21import hyperframe.frame
22
23from hypothesis import given
24from hypothesis.strategies import binary, lists, tuples
25
26HEADERS_STRATEGY = lists(tuples(binary(min_size=1), binary()))
27
28
29class TestInvalidFrameSequences(object):
30    """
31    Invalid header sequences cause ProtocolErrors to be thrown when received.
32    """
33    base_request_headers = [
34        (':authority', 'example.com'),
35        (':path', '/'),
36        (':scheme', 'https'),
37        (':method', 'GET'),
38        ('user-agent', 'someua/0.0.1'),
39    ]
40    invalid_header_blocks = [
41        base_request_headers + [('Uppercase', 'name')],
42        base_request_headers + [(':late', 'pseudo-header')],
43        [(':path', 'duplicate-pseudo-header')] + base_request_headers,
44        base_request_headers + [('connection', 'close')],
45        base_request_headers + [('proxy-connection', 'close')],
46        base_request_headers + [('keep-alive', 'close')],
47        base_request_headers + [('transfer-encoding', 'gzip')],
48        base_request_headers + [('upgrade', 'super-protocol/1.1')],
49        base_request_headers + [('te', 'chunked')],
50        base_request_headers + [('host', 'notexample.com')],
51        base_request_headers + [(' name', 'name with leading space')],
52        base_request_headers + [('name ', 'name with trailing space')],
53        base_request_headers + [('name', ' value with leading space')],
54        base_request_headers + [('name', 'value with trailing space ')],
55        [header for header in base_request_headers
56         if header[0] != ':authority'],
57        [(':protocol', 'websocket')] + base_request_headers,
58    ]
59    server_config = h2.config.H2Configuration(
60        client_side=False, header_encoding='utf-8'
61    )
62
63    @pytest.mark.parametrize('headers', invalid_header_blocks)
64    def test_headers_event(self, frame_factory, headers):
65        """
66        Test invalid headers are rejected with PROTOCOL_ERROR.
67        """
68        c = h2.connection.H2Connection(config=self.server_config)
69        c.receive_data(frame_factory.preamble())
70        c.clear_outbound_data_buffer()
71
72        f = frame_factory.build_headers_frame(headers)
73        data = f.serialize()
74
75        with pytest.raises(h2.exceptions.ProtocolError):
76            c.receive_data(data)
77
78        expected_frame = frame_factory.build_goaway_frame(
79            last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR
80        )
81        assert c.data_to_send() == expected_frame.serialize()
82
83    @pytest.mark.parametrize('headers', invalid_header_blocks)
84    def test_push_promise_event(self, frame_factory, headers):
85        """
86        If a PUSH_PROMISE header frame is received with an invalid header block
87        it is rejected with a PROTOCOL_ERROR.
88        """
89        c = h2.connection.H2Connection()
90        c.initiate_connection()
91        c.send_headers(
92            stream_id=1, headers=self.base_request_headers, end_stream=True
93        )
94        c.clear_outbound_data_buffer()
95
96        f = frame_factory.build_push_promise_frame(
97            stream_id=1,
98            promised_stream_id=2,
99            headers=headers
100        )
101        data = f.serialize()
102
103        with pytest.raises(h2.exceptions.ProtocolError):
104            c.receive_data(data)
105
106        expected_frame = frame_factory.build_goaway_frame(
107            last_stream_id=0, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR
108        )
109        assert c.data_to_send() == expected_frame.serialize()
110
111    @pytest.mark.parametrize('headers', invalid_header_blocks)
112    def test_push_promise_skipping_validation(self, frame_factory, headers):
113        """
114        If we have ``validate_inbound_headers`` disabled, then invalid header
115        blocks in push promise frames are allowed to pass.
116        """
117        config = h2.config.H2Configuration(
118            client_side=True,
119            validate_inbound_headers=False,
120            header_encoding='utf-8'
121        )
122
123        c = h2.connection.H2Connection(config=config)
124        c.initiate_connection()
125        c.send_headers(
126            stream_id=1, headers=self.base_request_headers, end_stream=True
127        )
128        c.clear_outbound_data_buffer()
129
130        f = frame_factory.build_push_promise_frame(
131            stream_id=1,
132            promised_stream_id=2,
133            headers=headers
134        )
135        data = f.serialize()
136
137        events = c.receive_data(data)
138        assert len(events) == 1
139        pp_event = events[0]
140        assert pp_event.headers == headers
141
142    @pytest.mark.parametrize('headers', invalid_header_blocks)
143    def test_headers_event_skipping_validation(self, frame_factory, headers):
144        """
145        If we have ``validate_inbound_headers`` disabled, then all of these
146        invalid header blocks are allowed to pass.
147        """
148        config = h2.config.H2Configuration(
149            client_side=False,
150            validate_inbound_headers=False,
151            header_encoding='utf-8'
152        )
153
154        c = h2.connection.H2Connection(config=config)
155        c.receive_data(frame_factory.preamble())
156
157        f = frame_factory.build_headers_frame(headers)
158        data = f.serialize()
159
160        events = c.receive_data(data)
161        assert len(events) == 1
162        request_event = events[0]
163        assert request_event.headers == headers
164
165    def test_transfer_encoding_trailers_is_valid(self, frame_factory):
166        """
167        Transfer-Encoding trailers is allowed by the filter.
168        """
169        headers = (
170            self.base_request_headers + [('te', 'trailers')]
171        )
172
173        c = h2.connection.H2Connection(config=self.server_config)
174        c.receive_data(frame_factory.preamble())
175
176        f = frame_factory.build_headers_frame(headers)
177        data = f.serialize()
178
179        events = c.receive_data(data)
180        assert len(events) == 1
181        request_event = events[0]
182        assert request_event.headers == headers
183
184    def test_pseudo_headers_rejected_in_trailer(self, frame_factory):
185        """
186        Ensure we reject pseudo headers included in trailers
187        """
188        trailers = [(':path', '/'), ('extra', 'value')]
189
190        c = h2.connection.H2Connection(config=self.server_config)
191        c.receive_data(frame_factory.preamble())
192        c.clear_outbound_data_buffer()
193
194        header_frame = frame_factory.build_headers_frame(
195            self.base_request_headers
196        )
197        trailer_frame = frame_factory.build_headers_frame(
198            trailers, flags=["END_STREAM"]
199        )
200        head = header_frame.serialize()
201        trailer = trailer_frame.serialize()
202
203        c.receive_data(head)
204        # Raise exception if pseudo header in trailer
205        with pytest.raises(h2.exceptions.ProtocolError) as e:
206            c.receive_data(trailer)
207        assert "pseudo-header in trailer" in str(e.value)
208
209        # Test appropriate response frame is generated
210        expected_frame = frame_factory.build_goaway_frame(
211            last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR
212        )
213        assert c.data_to_send() == expected_frame.serialize()
214
215
216class TestSendingInvalidFrameSequences(object):
217    """
218    Trying to send invalid header sequences cause ProtocolErrors to
219    be thrown.
220    """
221    base_request_headers = [
222        (':authority', 'example.com'),
223        (':path', '/'),
224        (':scheme', 'https'),
225        (':method', 'GET'),
226        ('user-agent', 'someua/0.0.1'),
227    ]
228    invalid_header_blocks = [
229        base_request_headers + [(':late', 'pseudo-header')],
230        [(':path', 'duplicate-pseudo-header')] + base_request_headers,
231        base_request_headers + [('te', 'chunked')],
232        base_request_headers + [('host', 'notexample.com')],
233        [header for header in base_request_headers
234         if header[0] != ':authority'],
235    ]
236    strippable_header_blocks = [
237        base_request_headers + [('connection', 'close')],
238        base_request_headers + [('proxy-connection', 'close')],
239        base_request_headers + [('keep-alive', 'close')],
240        base_request_headers + [('transfer-encoding', 'gzip')],
241        base_request_headers + [('upgrade', 'super-protocol/1.1')]
242    ]
243    all_header_blocks = invalid_header_blocks + strippable_header_blocks
244
245    server_config = h2.config.H2Configuration(client_side=False)
246
247    @pytest.mark.parametrize('headers', invalid_header_blocks)
248    def test_headers_event(self, frame_factory, headers):
249        """
250        Test sending invalid headers raise a ProtocolError.
251        """
252        c = h2.connection.H2Connection()
253        c.initiate_connection()
254
255        # Clear the data, then try to send headers.
256        c.clear_outbound_data_buffer()
257        with pytest.raises(h2.exceptions.ProtocolError):
258            c.send_headers(1, headers)
259
260    @pytest.mark.parametrize('headers', invalid_header_blocks)
261    def test_send_push_promise(self, frame_factory, headers):
262        """
263        Sending invalid headers in a push promise raises a ProtocolError.
264        """
265        c = h2.connection.H2Connection(config=self.server_config)
266        c.initiate_connection()
267        c.receive_data(frame_factory.preamble())
268
269        header_frame = frame_factory.build_headers_frame(
270            self.base_request_headers
271        )
272        c.receive_data(header_frame.serialize())
273
274        # Clear the data, then try to send a push promise.
275        c.clear_outbound_data_buffer()
276        with pytest.raises(h2.exceptions.ProtocolError):
277            c.push_stream(
278                stream_id=1, promised_stream_id=2, request_headers=headers
279            )
280
281    @pytest.mark.parametrize('headers', all_header_blocks)
282    def test_headers_event_skipping_validation(self, frame_factory, headers):
283        """
284        If we have ``validate_outbound_headers`` disabled, then all of these
285        invalid header blocks are allowed to pass.
286        """
287        config = h2.config.H2Configuration(
288            validate_outbound_headers=False
289        )
290
291        c = h2.connection.H2Connection(config=config)
292        c.initiate_connection()
293
294        # Clear the data, then send headers.
295        c.clear_outbound_data_buffer()
296        c.send_headers(1, headers)
297
298        # Ensure headers are still normalized.
299        norm_headers = h2.utilities.normalize_outbound_headers(headers, None)
300        f = frame_factory.build_headers_frame(norm_headers)
301        assert c.data_to_send() == f.serialize()
302
303    @pytest.mark.parametrize('headers', all_header_blocks)
304    def test_push_promise_skipping_validation(self, frame_factory, headers):
305        """
306        If we have ``validate_outbound_headers`` disabled, then all of these
307        invalid header blocks are allowed to pass.
308        """
309        config = h2.config.H2Configuration(
310            client_side=False,
311            validate_outbound_headers=False,
312        )
313
314        c = h2.connection.H2Connection(config=config)
315        c.initiate_connection()
316        c.receive_data(frame_factory.preamble())
317
318        header_frame = frame_factory.build_headers_frame(
319            self.base_request_headers
320        )
321        c.receive_data(header_frame.serialize())
322
323        # Create push promise frame with normalized headers.
324        frame_factory.refresh_encoder()
325        norm_headers = h2.utilities.normalize_outbound_headers(headers, None)
326        pp_frame = frame_factory.build_push_promise_frame(
327            stream_id=1, promised_stream_id=2, headers=norm_headers
328        )
329
330        # Clear the data, then send a push promise.
331        c.clear_outbound_data_buffer()
332        c.push_stream(
333            stream_id=1, promised_stream_id=2, request_headers=headers
334        )
335        assert c.data_to_send() == pp_frame.serialize()
336
337    @pytest.mark.parametrize('headers', all_header_blocks)
338    def test_headers_event_skip_normalization(self, frame_factory, headers):
339        """
340        If we have ``normalize_outbound_headers`` disabled, then all of these
341        invalid header blocks are sent through unmodified.
342        """
343        config = h2.config.H2Configuration(
344            validate_outbound_headers=False,
345            normalize_outbound_headers=False
346        )
347
348        c = h2.connection.H2Connection(config=config)
349        c.initiate_connection()
350
351        f = frame_factory.build_headers_frame(
352            headers,
353            stream_id=1,
354        )
355
356        # Clear the data, then send headers.
357        c.clear_outbound_data_buffer()
358        c.send_headers(1, headers)
359        assert c.data_to_send() == f.serialize()
360
361    @pytest.mark.parametrize('headers', all_header_blocks)
362    def test_push_promise_skip_normalization(self, frame_factory, headers):
363        """
364        If we have ``normalize_outbound_headers`` disabled, then all of these
365        invalid header blocks are allowed to pass unmodified.
366        """
367        config = h2.config.H2Configuration(
368            client_side=False,
369            validate_outbound_headers=False,
370            normalize_outbound_headers=False,
371        )
372
373        c = h2.connection.H2Connection(config=config)
374        c.initiate_connection()
375        c.receive_data(frame_factory.preamble())
376
377        header_frame = frame_factory.build_headers_frame(
378            self.base_request_headers
379        )
380        c.receive_data(header_frame.serialize())
381
382        frame_factory.refresh_encoder()
383        pp_frame = frame_factory.build_push_promise_frame(
384            stream_id=1, promised_stream_id=2, headers=headers
385        )
386
387        # Clear the data, then send a push promise.
388        c.clear_outbound_data_buffer()
389        c.push_stream(
390            stream_id=1, promised_stream_id=2, request_headers=headers
391        )
392        assert c.data_to_send() == pp_frame.serialize()
393
394    @pytest.mark.parametrize('headers', strippable_header_blocks)
395    def test_strippable_headers(self, frame_factory, headers):
396        """
397        Test connection related headers are removed before sending.
398        """
399        c = h2.connection.H2Connection()
400        c.initiate_connection()
401
402        # Clear the data, then try to send headers.
403        c.clear_outbound_data_buffer()
404        c.send_headers(1, headers)
405
406        f = frame_factory.build_headers_frame(self.base_request_headers)
407        assert c.data_to_send() == f.serialize()
408
409
410class TestFilter(object):
411    """
412    Test the filter function directly.
413
414    These tests exists to confirm the behaviour of the filter function in a
415    wide range of scenarios. Many of these scenarios may not be legal for
416    HTTP/2 and so may never hit the function, but it's worth validating that it
417    behaves as expected anyway.
418    """
419    validation_functions = [
420        h2.utilities.validate_headers,
421        h2.utilities.validate_outbound_headers
422    ]
423
424    hdr_validation_combos = [
425        h2.utilities.HeaderValidationFlags(
426            is_client, is_trailer, is_response_header, is_push_promise
427        )
428        for is_client, is_trailer, is_response_header, is_push_promise in (
429            itertools.product([True, False], repeat=4)
430        )
431    ]
432
433    hdr_validation_response_headers = [
434        flags for flags in hdr_validation_combos
435        if flags.is_response_header
436    ]
437
438    hdr_validation_request_headers_no_trailer = [
439        flags for flags in hdr_validation_combos
440        if not (flags.is_trailer or flags.is_response_header)
441    ]
442
443    invalid_request_header_blocks_bytes = (
444        # First, missing :method
445        (
446            (b':authority', b'google.com'),
447            (b':path', b'/'),
448            (b':scheme', b'https'),
449        ),
450        # Next, missing :path
451        (
452            (b':authority', b'google.com'),
453            (b':method', b'GET'),
454            (b':scheme', b'https'),
455        ),
456        # Next, missing :scheme
457        (
458            (b':authority', b'google.com'),
459            (b':method', b'GET'),
460            (b':path', b'/'),
461        ),
462        # Finally, path present but empty.
463        (
464            (b':authority', b'google.com'),
465            (b':method', b'GET'),
466            (b':scheme', b'https'),
467            (b':path', b''),
468        ),
469    )
470    invalid_request_header_blocks_unicode = (
471        # First, missing :method
472        (
473            (u':authority', u'google.com'),
474            (u':path', u'/'),
475            (u':scheme', u'https'),
476        ),
477        # Next, missing :path
478        (
479            (u':authority', u'google.com'),
480            (u':method', u'GET'),
481            (u':scheme', u'https'),
482        ),
483        # Next, missing :scheme
484        (
485            (u':authority', u'google.com'),
486            (u':method', u'GET'),
487            (u':path', u'/'),
488        ),
489        # Finally, path present but empty.
490        (
491            (u':authority', u'google.com'),
492            (u':method', u'GET'),
493            (u':scheme', u'https'),
494            (u':path', u''),
495        ),
496    )
497
498    # All headers that are forbidden from either request or response blocks.
499    forbidden_request_headers_bytes = (b':status',)
500    forbidden_request_headers_unicode = (u':status',)
501    forbidden_response_headers_bytes = (
502        b':path', b':scheme', b':authority', b':method'
503    )
504    forbidden_response_headers_unicode = (
505        u':path', u':scheme', u':authority', u':method'
506    )
507
508    @pytest.mark.parametrize('validation_function', validation_functions)
509    @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos)
510    @given(headers=HEADERS_STRATEGY)
511    def test_range_of_acceptable_outputs(self,
512                                         headers,
513                                         validation_function,
514                                         hdr_validation_flags):
515        """
516        The header validation functions either return the data unchanged
517        or throw a ProtocolError.
518        """
519        try:
520            assert headers == list(validation_function(
521                headers, hdr_validation_flags))
522        except h2.exceptions.ProtocolError:
523            assert True
524
525    @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos)
526    def test_invalid_pseudo_headers(self, hdr_validation_flags):
527        headers = [(b':custom', b'value')]
528        with pytest.raises(h2.exceptions.ProtocolError):
529            list(h2.utilities.validate_headers(headers, hdr_validation_flags))
530
531    @pytest.mark.parametrize('validation_function', validation_functions)
532    @pytest.mark.parametrize(
533        'hdr_validation_flags', hdr_validation_request_headers_no_trailer
534    )
535    def test_matching_authority_host_headers(self,
536                                             validation_function,
537                                             hdr_validation_flags):
538        """
539        If a header block has :authority and Host headers and they match,
540        the headers should pass through unchanged.
541        """
542        headers = [
543            (b':authority', b'example.com'),
544            (b':path', b'/'),
545            (b':scheme', b'https'),
546            (b':method', b'GET'),
547            (b'host', b'example.com'),
548        ]
549        assert headers == list(h2.utilities.validate_headers(
550            headers, hdr_validation_flags
551        ))
552
553    @pytest.mark.parametrize(
554        'hdr_validation_flags', hdr_validation_response_headers
555    )
556    def test_response_header_without_status(self, hdr_validation_flags):
557        headers = [(b'content-length', b'42')]
558        with pytest.raises(h2.exceptions.ProtocolError):
559            list(h2.utilities.validate_headers(headers, hdr_validation_flags))
560
561    @pytest.mark.parametrize(
562        'hdr_validation_flags', hdr_validation_request_headers_no_trailer
563    )
564    @pytest.mark.parametrize(
565        'header_block',
566        (
567            invalid_request_header_blocks_bytes +
568            invalid_request_header_blocks_unicode
569        )
570    )
571    def test_outbound_req_header_missing_pseudo_headers(self,
572                                                        hdr_validation_flags,
573                                                        header_block):
574        with pytest.raises(h2.exceptions.ProtocolError):
575            list(
576                h2.utilities.validate_outbound_headers(
577                    header_block, hdr_validation_flags
578                )
579            )
580
581    @pytest.mark.parametrize(
582        'hdr_validation_flags', hdr_validation_request_headers_no_trailer
583    )
584    @pytest.mark.parametrize(
585        'header_block', invalid_request_header_blocks_bytes
586    )
587    def test_inbound_req_header_missing_pseudo_headers(self,
588                                                       hdr_validation_flags,
589                                                       header_block):
590        with pytest.raises(h2.exceptions.ProtocolError):
591            list(
592                h2.utilities.validate_headers(
593                    header_block, hdr_validation_flags
594                )
595            )
596
597    @pytest.mark.parametrize(
598        'hdr_validation_flags', hdr_validation_request_headers_no_trailer
599    )
600    @pytest.mark.parametrize(
601        'invalid_header',
602        forbidden_request_headers_bytes + forbidden_request_headers_unicode
603    )
604    def test_outbound_req_header_extra_pseudo_headers(self,
605                                                      hdr_validation_flags,
606                                                      invalid_header):
607        """
608        Outbound request header blocks containing the forbidden request headers
609        fail validation.
610        """
611        headers = [
612            (b':path', b'/'),
613            (b':scheme', b'https'),
614            (b':authority', b'google.com'),
615            (b':method', b'GET'),
616        ]
617        headers.append((invalid_header, b'some value'))
618        with pytest.raises(h2.exceptions.ProtocolError):
619            list(
620                h2.utilities.validate_outbound_headers(
621                    headers, hdr_validation_flags
622                )
623            )
624
625    @pytest.mark.parametrize(
626        'hdr_validation_flags', hdr_validation_request_headers_no_trailer
627    )
628    @pytest.mark.parametrize(
629        'invalid_header',
630        forbidden_request_headers_bytes
631    )
632    def test_inbound_req_header_extra_pseudo_headers(self,
633                                                     hdr_validation_flags,
634                                                     invalid_header):
635        """
636        Inbound request header blocks containing the forbidden request headers
637        fail validation.
638        """
639        headers = [
640            (b':path', b'/'),
641            (b':scheme', b'https'),
642            (b':authority', b'google.com'),
643            (b':method', b'GET'),
644        ]
645        headers.append((invalid_header, b'some value'))
646        with pytest.raises(h2.exceptions.ProtocolError):
647            list(h2.utilities.validate_headers(headers, hdr_validation_flags))
648
649    @pytest.mark.parametrize(
650        'hdr_validation_flags', hdr_validation_response_headers
651    )
652    @pytest.mark.parametrize(
653        'invalid_header',
654        forbidden_response_headers_bytes + forbidden_response_headers_unicode
655    )
656    def test_outbound_resp_header_extra_pseudo_headers(self,
657                                                       hdr_validation_flags,
658                                                       invalid_header):
659        """
660        Outbound response header blocks containing the forbidden response
661        headers fail validation.
662        """
663        headers = [(b':status', b'200')]
664        headers.append((invalid_header, b'some value'))
665        with pytest.raises(h2.exceptions.ProtocolError):
666            list(
667                h2.utilities.validate_outbound_headers(
668                    headers, hdr_validation_flags
669                )
670            )
671
672    @pytest.mark.parametrize(
673        'hdr_validation_flags', hdr_validation_response_headers
674    )
675    @pytest.mark.parametrize(
676        'invalid_header',
677        forbidden_response_headers_bytes
678    )
679    def test_inbound_resp_header_extra_pseudo_headers(self,
680                                                      hdr_validation_flags,
681                                                      invalid_header):
682        """
683        Inbound response header blocks containing the forbidden response
684        headers fail validation.
685        """
686        headers = [(b':status', b'200')]
687        headers.append((invalid_header, b'some value'))
688        with pytest.raises(h2.exceptions.ProtocolError):
689            list(h2.utilities.validate_headers(headers, hdr_validation_flags))
690
691
692class TestOversizedHeaders(object):
693    """
694    Tests that oversized header blocks are correctly rejected. This replicates
695    the "HPACK Bomb" attack, and confirms that we're resistant against it.
696    """
697    request_header_block = [
698        (b':method', b'GET'),
699        (b':authority', b'example.com'),
700        (b':scheme', b'https'),
701        (b':path', b'/'),
702    ]
703
704    response_header_block = [
705        (b':status', b'200'),
706    ]
707
708    # The first header block contains a single header that fills the header
709    # table. To do that, we'll give it a single-character header name and a
710    # 4063 byte header value. This will make it exactly the size of the header
711    # table. It must come last, so that it evicts all other headers.
712    # This block must be appended to either a request or response block.
713    first_header_block = [
714        (b'a', b'a' * 4063),
715    ]
716
717    # The second header "block" is actually a custom HEADERS frame body that
718    # simply repeatedly refers to the first entry for 16kB. Each byte has the
719    # high bit set (0x80), and then uses the remaining 7 bits to encode the
720    # number 62 (0x3e), leading to a repeat of the byte 0xbe.
721    second_header_block = b'\xbe' * 2**14
722
723    server_config = h2.config.H2Configuration(client_side=False)
724
725    def test_hpack_bomb_request(self, frame_factory):
726        """
727        A HPACK bomb request causes the connection to be torn down with the
728        error code ENHANCE_YOUR_CALM.
729        """
730        c = h2.connection.H2Connection(config=self.server_config)
731        c.receive_data(frame_factory.preamble())
732        c.clear_outbound_data_buffer()
733
734        f = frame_factory.build_headers_frame(
735            self.request_header_block + self.first_header_block
736        )
737        data = f.serialize()
738        c.receive_data(data)
739
740        # Build the attack payload.
741        attack_frame = hyperframe.frame.HeadersFrame(stream_id=3)
742        attack_frame.data = self.second_header_block
743        attack_frame.flags.add('END_HEADERS')
744        data = attack_frame.serialize()
745
746        with pytest.raises(h2.exceptions.DenialOfServiceError):
747            c.receive_data(data)
748
749        expected_frame = frame_factory.build_goaway_frame(
750            last_stream_id=1, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM
751        )
752        assert c.data_to_send() == expected_frame.serialize()
753
754    def test_hpack_bomb_response(self, frame_factory):
755        """
756        A HPACK bomb response causes the connection to be torn down with the
757        error code ENHANCE_YOUR_CALM.
758        """
759        c = h2.connection.H2Connection()
760        c.initiate_connection()
761        c.send_headers(
762            stream_id=1, headers=self.request_header_block
763        )
764        c.send_headers(
765            stream_id=3, headers=self.request_header_block
766        )
767        c.clear_outbound_data_buffer()
768
769        f = frame_factory.build_headers_frame(
770            self.response_header_block + self.first_header_block
771        )
772        data = f.serialize()
773        c.receive_data(data)
774
775        # Build the attack payload.
776        attack_frame = hyperframe.frame.HeadersFrame(stream_id=3)
777        attack_frame.data = self.second_header_block
778        attack_frame.flags.add('END_HEADERS')
779        data = attack_frame.serialize()
780
781        with pytest.raises(h2.exceptions.DenialOfServiceError):
782            c.receive_data(data)
783
784        expected_frame = frame_factory.build_goaway_frame(
785            last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM
786        )
787        assert c.data_to_send() == expected_frame.serialize()
788
789    def test_hpack_bomb_push(self, frame_factory):
790        """
791        A HPACK bomb push causes the connection to be torn down with the
792        error code ENHANCE_YOUR_CALM.
793        """
794        c = h2.connection.H2Connection()
795        c.initiate_connection()
796        c.send_headers(
797            stream_id=1, headers=self.request_header_block
798        )
799        c.clear_outbound_data_buffer()
800
801        f = frame_factory.build_headers_frame(
802            self.response_header_block + self.first_header_block
803        )
804        data = f.serialize()
805        c.receive_data(data)
806
807        # Build the attack payload. We need to shrink it by four bytes because
808        # the promised_stream_id consumes four bytes of body.
809        attack_frame = hyperframe.frame.PushPromiseFrame(stream_id=3)
810        attack_frame.promised_stream_id = 2
811        attack_frame.data = self.second_header_block[:-4]
812        attack_frame.flags.add('END_HEADERS')
813        data = attack_frame.serialize()
814
815        with pytest.raises(h2.exceptions.DenialOfServiceError):
816            c.receive_data(data)
817
818        expected_frame = frame_factory.build_goaway_frame(
819            last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM
820        )
821        assert c.data_to_send() == expected_frame.serialize()
822
823    def test_reject_headers_when_list_size_shrunk(self, frame_factory):
824        """
825        When we've shrunk the header list size, we reject new header blocks
826        that violate the new size.
827        """
828        c = h2.connection.H2Connection(config=self.server_config)
829        c.receive_data(frame_factory.preamble())
830        c.clear_outbound_data_buffer()
831
832        # Receive the first request, which causes no problem.
833        f = frame_factory.build_headers_frame(
834            stream_id=1,
835            headers=self.request_header_block
836        )
837        data = f.serialize()
838        c.receive_data(data)
839
840        # Now, send a settings change. It's un-ACKed at this time. A new
841        # request arrives, also without incident.
842        c.update_settings({h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 50})
843        c.clear_outbound_data_buffer()
844        f = frame_factory.build_headers_frame(
845            stream_id=3,
846            headers=self.request_header_block
847        )
848        data = f.serialize()
849        c.receive_data(data)
850
851        # We get a SETTINGS ACK.
852        f = frame_factory.build_settings_frame({}, ack=True)
853        data = f.serialize()
854        c.receive_data(data)
855
856        # Now a third request comes in. This explodes.
857        f = frame_factory.build_headers_frame(
858            stream_id=5,
859            headers=self.request_header_block
860        )
861        data = f.serialize()
862
863        with pytest.raises(h2.exceptions.DenialOfServiceError):
864            c.receive_data(data)
865
866        expected_frame = frame_factory.build_goaway_frame(
867            last_stream_id=3, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM
868        )
869        assert c.data_to_send() == expected_frame.serialize()
870
871    def test_reject_headers_when_table_size_shrunk(self, frame_factory):
872        """
873        When we've shrunk the header table size, we reject header blocks that
874        do not respect the change.
875        """
876        c = h2.connection.H2Connection(config=self.server_config)
877        c.receive_data(frame_factory.preamble())
878        c.clear_outbound_data_buffer()
879
880        # Receive the first request, which causes no problem.
881        f = frame_factory.build_headers_frame(
882            stream_id=1,
883            headers=self.request_header_block
884        )
885        data = f.serialize()
886        c.receive_data(data)
887
888        # Now, send a settings change. It's un-ACKed at this time. A new
889        # request arrives, also without incident.
890        c.update_settings({h2.settings.SettingCodes.HEADER_TABLE_SIZE: 128})
891        c.clear_outbound_data_buffer()
892        f = frame_factory.build_headers_frame(
893            stream_id=3,
894            headers=self.request_header_block
895        )
896        data = f.serialize()
897        c.receive_data(data)
898
899        # We get a SETTINGS ACK.
900        f = frame_factory.build_settings_frame({}, ack=True)
901        data = f.serialize()
902        c.receive_data(data)
903
904        # Now a third request comes in. This explodes, as it does not contain
905        # a dynamic table size update.
906        f = frame_factory.build_headers_frame(
907            stream_id=5,
908            headers=self.request_header_block
909        )
910        data = f.serialize()
911
912        with pytest.raises(h2.exceptions.ProtocolError):
913            c.receive_data(data)
914
915        expected_frame = frame_factory.build_goaway_frame(
916            last_stream_id=3, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR
917        )
918        assert c.data_to_send() == expected_frame.serialize()
919
920    def test_reject_headers_exceeding_table_size(self, frame_factory):
921        """
922        When the remote peer sends a dynamic table size update that exceeds our
923        setting, we reject it.
924        """
925        c = h2.connection.H2Connection(config=self.server_config)
926        c.receive_data(frame_factory.preamble())
927        c.clear_outbound_data_buffer()
928
929        # Receive the first request, which causes no problem.
930        f = frame_factory.build_headers_frame(
931            stream_id=1,
932            headers=self.request_header_block
933        )
934        data = f.serialize()
935        c.receive_data(data)
936
937        # Now a second request comes in that sets the table size too high.
938        # This explodes.
939        frame_factory.change_table_size(c.local_settings.header_table_size + 1)
940        f = frame_factory.build_headers_frame(
941            stream_id=5,
942            headers=self.request_header_block
943        )
944        data = f.serialize()
945
946        with pytest.raises(h2.exceptions.ProtocolError):
947            c.receive_data(data)
948
949        expected_frame = frame_factory.build_goaway_frame(
950            last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR
951        )
952        assert c.data_to_send() == expected_frame.serialize()
953