summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIra Lun <sammyrosajoe@gmail.com>2017-08-29 20:48:06 +0100
committerIra Lun <sammyrosajoe@gmail.com>2017-08-29 20:48:06 +0100
commit1e7da61aabaeb8a3312d4e67321fcb799d4fa26a (patch)
tree52286a28bac8d1ee961408a8002d0fabc7b80746
parent753ff4edb5ecc5b7a191ff51f49b2b3c51deeea8 (diff)
downloadwebob-1e7da61aabaeb8a3312d4e67321fcb799d4fa26a.tar.gz
Add AcceptCharsetInvalidHeader class, docs and tests.
-rw-r--r--docs/api/webob.txt5
-rw-r--r--src/webob/acceptparse.py111
-rw-r--r--tests/test_acceptparse.py213
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', [
'"',