diff --git a/HISTORY.rst b/HISTORY.rst index ba8d9b1..5f477a3 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,6 +6,10 @@ Release History **API Changes (Backward Compatible)** +- Added a new ``ZeroLengthHeaderNameError`` that indicates that an + invalid header has been received. This places the HPACK decoder into + a broken state: it must not be used after this exception is thrown. + **Bugfixes** - Performance improvement of static header search. Use dict search instead diff --git a/hpack/__init__.py b/hpack/__init__.py index 7485faf..ab05c80 100644 --- a/hpack/__init__.py +++ b/hpack/__init__.py @@ -8,13 +8,14 @@ from .hpack import Encoder, Decoder from .struct import HeaderTuple, NeverIndexedHeaderTuple from .exceptions import ( - HPACKError, HPACKDecodingError, InvalidTableIndex, OversizedHeaderListError + HPACKError, HPACKDecodingError, InvalidTableIndex, + OversizedHeaderListError, ZeroLengthHeaderNameError, ) __all__ = [ 'Encoder', 'Decoder', 'HPACKError', 'HPACKDecodingError', 'InvalidTableIndex', 'HeaderTuple', 'NeverIndexedHeaderTuple', - 'OversizedHeaderListError' + 'OversizedHeaderListError', 'ZeroLengthHeaderNameError', ] __version__ = '3.1.0dev0' diff --git a/hpack/exceptions.py b/hpack/exceptions.py index 571ba98..2cdd16c 100644 --- a/hpack/exceptions.py +++ b/hpack/exceptions.py @@ -47,3 +47,12 @@ class InvalidTableSizeError(HPACKDecodingError): .. versionadded:: 3.0.0 """ pass + + +class ZeroLengthHeaderNameError(HPACKDecodingError): + """ + An invalid header with a zero length name has been received. + + .. versionadded:: 3.1.0 + """ + pass diff --git a/hpack/hpack.py b/hpack/hpack.py index d5c5001..c0de505 100644 --- a/hpack/hpack.py +++ b/hpack/hpack.py @@ -10,7 +10,8 @@ from .table import HeaderTable, table_entry_size from .compat import to_byte, to_bytes from .exceptions import ( - HPACKDecodingError, OversizedHeaderListError, InvalidTableSizeError + HPACKDecodingError, OversizedHeaderListError, InvalidTableSizeError, + ZeroLengthHeaderNameError, ) from .huffman import HuffmanEncoder from .huffman_constants import ( @@ -496,6 +497,7 @@ def decode(self, data, raw=False): if header: headers.append(header) + self._assert_valid_header_name_size(header) inflated_size += table_entry_size(*header) if inflated_size > self.max_header_list_size: @@ -516,6 +518,13 @@ def decode(self, data, raw=False): except UnicodeDecodeError: raise HPACKDecodingError("Unable to decode headers as UTF-8.") + def _assert_valid_header_name_size(self, header): + """ + Check that the header name size is valid, i.e. non-zero. + """ + if len(header[0]) == 0: + raise ZeroLengthHeaderNameError() + def _assert_valid_table_size(self): """ Check that the table size set by the encoder is lower than the maximum diff --git a/test/test_hpack.py b/test/test_hpack.py index c3333b4..dda19a7 100644 --- a/test/test_hpack.py +++ b/test/test_hpack.py @@ -2,7 +2,7 @@ from hpack.hpack import Encoder, Decoder, _dict_to_iterable, _to_bytes from hpack.exceptions import ( HPACKDecodingError, InvalidTableIndex, OversizedHeaderListError, - InvalidTableSizeError + InvalidTableSizeError, ZeroLengthHeaderNameError, ) from hpack.struct import HeaderTuple, NeverIndexedHeaderTuple import itertools @@ -638,6 +638,16 @@ def test_max_header_list_size(self): with pytest.raises(OversizedHeaderListError): d.decode(data) + def test_zero_length_header(self): + """ + If a header has a name of zero length it is invalid and the HPACK + decoder raises a ZeroLengthHeaderNameError. + """ + d = Decoder(max_header_list_size=44) + data = b"@\x80\x80" + with pytest.raises(ZeroLengthHeaderNameError): + d.decode(data) + def test_can_decode_multiple_header_table_size_changes(self): """ If multiple header table size changes are sent in at once, they are