diff options
| author | Ira Lun <sammyrosajoe@gmail.com> | 2017-08-29 20:48:06 +0100 |
|---|---|---|
| committer | Ira Lun <sammyrosajoe@gmail.com> | 2017-08-29 20:48:06 +0100 |
| commit | 1e7da61aabaeb8a3312d4e67321fcb799d4fa26a (patch) | |
| tree | 52286a28bac8d1ee961408a8002d0fabc7b80746 | |
| parent | 753ff4edb5ecc5b7a191ff51f49b2b3c51deeea8 (diff) | |
| download | webob-1e7da61aabaeb8a3312d4e67321fcb799d4fa26a.tar.gz | |
Add AcceptCharsetInvalidHeader class, docs and tests.
| -rw-r--r-- | docs/api/webob.txt | 5 | ||||
| -rw-r--r-- | src/webob/acceptparse.py | 111 | ||||
| -rw-r--r-- | tests/test_acceptparse.py | 213 |
3 files changed, 329 insertions, 0 deletions
diff --git a/docs/api/webob.txt b/docs/api/webob.txt index e80d766..cf15f08 100644 --- a/docs/api/webob.txt +++ b/docs/api/webob.txt @@ -21,6 +21,11 @@ Accept-* __contains__, __iter__, __nonzero__, __radd__, __repr__, __str__, acceptable_offers, best_match, quality +.. autoclass:: AcceptCharsetInvalidHeader + :members: parse, header_value, parsed, __init__, __add__, __bool__, + __contains__, __iter__, __nonzero__, __radd__, __repr__, __str__, + acceptable_offers, best_match, quality + .. autofunction:: create_accept_encoding_header diff --git a/src/webob/acceptparse.py b/src/webob/acceptparse.py index 64ce067..824530e 100644 --- a/src/webob/acceptparse.py +++ b/src/webob/acceptparse.py @@ -1058,6 +1058,117 @@ class AcceptCharsetNoHeader(_AcceptCharsetInvalidOrNoHeader): return self.__class__() +class AcceptCharsetInvalidHeader(_AcceptCharsetInvalidOrNoHeader): + """ + Represent an invalid ``Accept-Charset`` header. + + An invalid header is one that does not conform to + :rfc:`7231#section-5.3.3`. As specified in the RFC, an empty header is an + invalid ``Accept-Charset`` header. + + :rfc:`7231` does not provide any guidance on what should happen if the + ``Accept-Charset`` header has an invalid value. This implementation + disregards the header, and treats it as if there is no ``Accept-Charset`` + header in the request. + + This object should not be modified. To add to the header, we can use the + addition operators (``+`` and ``+=``), which return a new object (see the + docstring for :meth:`AcceptCharsetInvalidHeader.__add__`). + """ + + @property + def header_value(self): + """(``str`` or ``None``) The header value.""" + return self._header_value + + @property + def parsed(self): + """ + (``list`` or ``None``) Parsed form of the header. + + As the header is invalid and cannot be parsed, this is ``None``. + """ + return self._parsed + + def __init__(self, header_value): + """ + Create an :class:`AcceptCharsetInvalidHeader` instance. + """ + self._header_value = header_value + self._parsed = None + self._parsed_nonzero = None + + def __add__(self, other): + """ + Add to header, creating a new header object. + + `other` can be: + + * ``None`` + * a ``str`` header value + * a ``dict``, where keys are charsets and values are qvalues + * a ``tuple`` or ``list``, where each item is a charset ``str`` or a + ``tuple`` or ``list`` (charset, qvalue) pair (``str``\ s and pairs + can be mixed within the ``tuple`` or ``list``) + * an :class:`AcceptCharsetValidHeader`, :class:`AcceptCharsetNoHeader`, + or :class:`AcceptCharsetInvalidHeader` instance + * object of any other type that returns a value for ``__str__`` + + If `other` is a valid header value or an + :class:`AcceptCharsetValidHeader` instance, a new + :class:`AcceptCharsetValidHeader` instance with the valid header value + is returned. + + If `other` is ``None``, an :class:`AcceptCharsetNoHeader` instance, an + invalid header value, or an :class:`AcceptCharsetInvalidHeader` + instance, a new :class:`AcceptCharsetNoHeader` instance is returned. + """ + if isinstance(other, AcceptCharsetValidHeader): + return AcceptCharsetValidHeader(header_value=other.header_value) + + if isinstance( + other, (AcceptCharsetNoHeader, AcceptCharsetInvalidHeader) + ): + return AcceptCharsetNoHeader() + + return self._add_instance_and_non_accept_charset_type( + instance=self, other=other, + ) + + def __radd__(self, other): + """ + Add to header, creating a new header object. + + See the docstring for :meth:`AcceptCharsetValidHeader.__add__`. + """ + return self._add_instance_and_non_accept_charset_type( + instance=self, other=other, instance_on_the_right=True, + ) + + def __repr__(self): + return '<{}>'.format(self.__class__.__name__) + # We do not display the header_value, as it is untrusted input. The + # header_value could always be easily obtained from the .header_value + # property. + + def __str__(self): + """Return the ``str`` ``'<invalid header value>'``.""" + return '<invalid header value>' + + def _add_instance_and_non_accept_charset_type( + self, instance, other, instance_on_the_right=False, + ): + if not other: + return AcceptCharsetNoHeader() + + other_header_value = self._python_value_to_header_str(value=other) + + try: + return AcceptCharsetValidHeader(header_value=other_header_value) + except ValueError: # invalid header value + return AcceptCharsetNoHeader() + + class AcceptEncoding(object): """ Represent an ``Accept-Encoding`` header. diff --git a/tests/test_acceptparse.py b/tests/test_acceptparse.py index 7d529e5..0930f26 100644 --- a/tests/test_acceptparse.py +++ b/tests/test_acceptparse.py @@ -7,6 +7,7 @@ from webob.acceptparse import ( _list_1_or_more__compiled_re, Accept, AcceptCharset, + AcceptCharsetInvalidHeader, AcceptCharsetNoHeader, AcceptCharsetValidHeader, AcceptEncoding, @@ -1101,6 +1102,218 @@ class TestAcceptCharsetNoHeader(object): assert returned == 1.0 +class TestAcceptCharsetInvalidHeader(object): + def test_parse__inherited(self): + returned = AcceptCharsetInvalidHeader.parse( + value=',iso-8859-5 ; q=0.333 , ,utf-8,unicode-1-1 ;q=0.90,', + ) + list_of_returned = list(returned) + assert list_of_returned == [ + ('iso-8859-5', 0.333), + ('utf-8', 1.0), + ('unicode-1-1', 0.9), + ] + + def test___init__(self): + header_value = 'invalid header' + instance = AcceptCharsetInvalidHeader(header_value=header_value) + assert instance.header_value == header_value + assert instance.parsed is None + assert instance._parsed_nonzero is None + assert isinstance(instance, AcceptCharset) + + def test___add___None(self): + instance = AcceptCharsetInvalidHeader(header_value='') + result = instance + None + assert isinstance(result, AcceptCharsetNoHeader) + + @pytest.mark.parametrize('right_operand', [ + '', + [], + (), + {}, + 'UTF/8', + ['UTF/8'], + ('UTF/8',), + {'UTF/8': 1.0}, + ]) + def test___add___invalid_value(self, right_operand): + result = AcceptCharsetInvalidHeader(header_value='') + right_operand + assert isinstance(result, AcceptCharsetNoHeader) + + @pytest.mark.parametrize('str_', ['', 'UTF/8']) + def test___add___other_type_with_invalid___str__(self, str_): + class Other(object): + def __str__(self): + return str_ + result = AcceptCharsetInvalidHeader(header_value='') + Other() + assert isinstance(result, AcceptCharsetNoHeader) + + @pytest.mark.parametrize('value, value_as_header', [ + ( + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + ), + ( + [('UTF-7', 0.5), ('unicode-1-1', 0.0), 'UTF-8'], + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + ), + ( + (('UTF-7', 0.5), ('unicode-1-1', 0.0), 'UTF-8'), + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + ), + ( + {'UTF-7': 0.5, 'unicode-1-1': 0.0, 'UTF-8': 1.0}, + 'UTF-8, UTF-7;q=0.5, unicode-1-1;q=0', + ), + ]) + def test___add___valid_header_value(self, value, value_as_header): + result = AcceptCharsetInvalidHeader(header_value='') + value + assert isinstance(result, AcceptCharsetValidHeader) + assert result.header_value == value_as_header + + def test___add___other_type_valid_header_value(self): + class Other(object): + def __str__(self): + return 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8' + right_operand = Other() + result = AcceptCharsetInvalidHeader(header_value='') + right_operand + assert isinstance(result, AcceptCharsetValidHeader) + assert result.header_value == str(right_operand) + + def test___add___AcceptCharsetValidHeader(self): + right_operand = AcceptCharsetValidHeader( + header_value=', ,utf-7;q=0, \tutf-8;q=1,', + ) + result = AcceptCharsetInvalidHeader(header_value='') + right_operand + assert isinstance(result, AcceptCharsetValidHeader) + assert result.header_value == right_operand.header_value + assert result is not right_operand + + def test___add___AcceptCharsetNoHeader(self): + right_operand = AcceptCharsetNoHeader() + result = AcceptCharsetInvalidHeader(header_value='') + right_operand + assert isinstance(result, AcceptCharsetNoHeader) + assert result is not right_operand + + def test___add___AcceptCharsetInvalidHeader(self): + result = AcceptCharsetInvalidHeader(header_value='') + \ + AcceptCharsetInvalidHeader(header_value='utf/8') + assert isinstance(result, AcceptCharsetNoHeader) + + def test___bool__(self): + instance = AcceptCharsetInvalidHeader(header_value='') + returned = bool(instance) + assert returned is False + + def test___contains__(self): + instance = AcceptCharsetInvalidHeader(header_value='') + returned = ('char-set' in instance) + assert returned is True + + def test___iter__(self): + instance = AcceptCharsetInvalidHeader(header_value='') + returned = list(instance) + assert returned == [] + + def test___radd___None(self): + result = None + AcceptCharsetInvalidHeader(header_value='') + assert isinstance(result, AcceptCharsetNoHeader) + + @pytest.mark.parametrize('left_operand', [ + '', + [], + (), + {}, + 'UTF/8', + ['UTF/8'], + ('UTF/8',), + {'UTF/8': 1.0}, + ]) + def test___radd___invalid_value(self, left_operand): + result = left_operand + AcceptCharsetInvalidHeader(header_value='') + assert isinstance(result, AcceptCharsetNoHeader) + + @pytest.mark.parametrize('str_', ['', 'UTF/8']) + def test___radd___other_type_with_invalid___str__(self, str_): + class Other(object): + def __str__(self): + return str_ + result = Other() + AcceptCharsetInvalidHeader(header_value='') + assert isinstance(result, AcceptCharsetNoHeader) + + @pytest.mark.parametrize('value, value_as_header', [ + ( + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + ), + ( + [('UTF-7', 0.5), ('unicode-1-1', 0.0), 'UTF-8'], + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + ), + ( + (('UTF-7', 0.5), ('unicode-1-1', 0.0), 'UTF-8'), + 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8', + ), + ( + {'UTF-7': 0.5, 'unicode-1-1': 0.0, 'UTF-8': 1.0}, + 'UTF-8, UTF-7;q=0.5, unicode-1-1;q=0', + ), + ]) + def test___radd___valid_header_value(self, value, value_as_header): + result = value + AcceptCharsetInvalidHeader(header_value='') + assert isinstance(result, AcceptCharsetValidHeader) + assert result.header_value == value_as_header + + def test___radd___other_type_valid_header_value(self): + class Other(object): + def __str__(self): + return 'UTF-7;q=0.5, unicode-1-1;q=0, UTF-8' + left_operand = Other() + result = left_operand + AcceptCharsetInvalidHeader(header_value='') + assert isinstance(result, AcceptCharsetValidHeader) + assert result.header_value == str(left_operand) + + def test___repr__(self): + instance = AcceptCharsetInvalidHeader(header_value='\x00') + assert repr(instance) == '<AcceptCharsetInvalidHeader>' + + def test___str__(self): + instance = AcceptCharsetInvalidHeader(header_value='') + assert str(instance) == '<invalid header value>' + + def test_acceptable_offers(self): + instance = AcceptCharsetInvalidHeader(header_value='') + returned = instance.acceptable_offers( + offers=['utf-8', 'utf-7', 'unicode-1-1'], + ) + assert returned == [ + ('utf-8', 1.0), ('utf-7', 1.0), ('unicode-1-1', 1.0) + ] + + def test_best_match(self): + accept = AcceptCharsetInvalidHeader(header_value='') + assert accept.best_match(['utf-8', 'iso-8859-5']) == 'utf-8' + assert accept.best_match([('utf-8', 1), ('iso-8859-5', 0.5)]) == \ + 'utf-8' + assert accept.best_match([('utf-8', 0.5), ('iso-8859-5', 1)]) == \ + 'iso-8859-5' + assert accept.best_match([('utf-8', 0.5), 'iso-8859-5']) == \ + 'iso-8859-5' + assert accept.best_match( + [('utf-8', 0.5), 'iso-8859-5'], default_match=True + ) == 'iso-8859-5' + assert accept.best_match( + [('utf-8', 0.5), 'iso-8859-5'], default_match=False + ) == 'iso-8859-5' + assert accept.best_match([], default_match='fallback') == 'fallback' + + def test_quality(self): + instance = AcceptCharsetInvalidHeader(header_value='') + returned = instance.quality(offer='char-set') + assert returned == 1.0 + + class TestAcceptEncoding(object): @pytest.mark.parametrize('value', [ '"', |
