diff options
Diffstat (limited to 'netlib/http/headers.py')
| -rw-r--r-- | netlib/http/headers.py | 140 | 
1 files changed, 43 insertions, 97 deletions
diff --git a/netlib/http/headers.py b/netlib/http/headers.py index 72739f90..60d3f429 100644 --- a/netlib/http/headers.py +++ b/netlib/http/headers.py @@ -1,9 +1,3 @@ -""" - -Unicode Handling ----------------- -See also: http://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ -"""  from __future__ import absolute_import, print_function, division  import re @@ -13,23 +7,22 @@ try:  except ImportError:  # pragma: no cover      from collections import MutableMapping  # Workaround for Python < 3.3 -  import six +from ..multidict import MultiDict +from ..utils import always_bytes -from netlib.utils import always_byte_args, always_bytes, Serializable +# See also: http://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/  if six.PY2:  # pragma: no cover      _native = lambda x: x      _always_bytes = lambda x: x -    _always_byte_args = lambda x: x  else:      # While headers _should_ be ASCII, it's not uncommon for certain headers to be utf-8 encoded.      _native = lambda x: x.decode("utf-8", "surrogateescape")      _always_bytes = lambda x: always_bytes(x, "utf-8", "surrogateescape") -    _always_byte_args = always_byte_args("utf-8", "surrogateescape") -class Headers(MutableMapping, Serializable): +class Headers(MultiDict):      """      Header class which allows both convenient access to individual headers as well as      direct access to the underlying raw data. Provides a full dictionary interface. @@ -49,11 +42,11 @@ class Headers(MutableMapping, Serializable):          >>> h["host"]          "example.com" -        # Headers can also be creatd from a list of raw (header_name, header_value) byte tuples +        # Headers can also be created from a list of raw (header_name, header_value) byte tuples          >>> h = Headers([ -            [b"Host",b"example.com"], -            [b"Accept",b"text/html"], -            [b"accept",b"application/xml"] +            (b"Host",b"example.com"), +            (b"Accept",b"text/html"), +            (b"accept",b"application/xml")          ])          # Multiple headers are folded into a single header as per RFC7230 @@ -77,7 +70,6 @@ class Headers(MutableMapping, Serializable):          For use with the "Set-Cookie" header, see :py:meth:`get_all`.      """ -    @_always_byte_args      def __init__(self, fields=None, **headers):          """          Args: @@ -89,19 +81,29 @@ class Headers(MutableMapping, Serializable):                  If ``**headers`` contains multiple keys that have equal ``.lower()`` s,                  the behavior is undefined.          """ -        self.fields = fields or [] +        super(Headers, self).__init__(fields) -        for name, value in self.fields: -            if not isinstance(name, bytes) or not isinstance(value, bytes): -                raise ValueError("Headers passed as fields must be bytes.") +        for key, value in self.fields: +            if not isinstance(key, bytes) or not isinstance(value, bytes): +                raise TypeError("Header fields must be bytes.")          # content_type -> content-type          headers = { -            _always_bytes(name).replace(b"_", b"-"): value +            _always_bytes(name).replace(b"_", b"-"): _always_bytes(value)              for name, value in six.iteritems(headers)              }          self.update(headers) +    @staticmethod +    def _reduce_values(values): +        # Headers can be folded +        return ", ".join(values) + +    @staticmethod +    def _kconv(key): +        # Headers are case-insensitive +        return key.lower() +      def __bytes__(self):          if self.fields:              return b"\r\n".join(b": ".join(field) for field in self.fields) + b"\r\n" @@ -111,98 +113,40 @@ class Headers(MutableMapping, Serializable):      if six.PY2:  # pragma: no cover          __str__ = __bytes__ -    @_always_byte_args -    def __getitem__(self, name): -        values = self.get_all(name) -        if not values: -            raise KeyError(name) -        return ", ".join(values) - -    @_always_byte_args -    def __setitem__(self, name, value): -        idx = self._index(name) - -        # To please the human eye, we insert at the same position the first existing header occured. -        if idx is not None: -            del self[name] -            self.fields.insert(idx, [name, value]) -        else: -            self.fields.append([name, value]) - -    @_always_byte_args -    def __delitem__(self, name): -        if name not in self: -            raise KeyError(name) -        name = name.lower() -        self.fields = [ -            field for field in self.fields -            if name != field[0].lower() -        ] +    def __delitem__(self, key): +        key = _always_bytes(key) +        super(Headers, self).__delitem__(key)      def __iter__(self): -        seen = set() -        for name, _ in self.fields: -            name_lower = name.lower() -            if name_lower not in seen: -                seen.add(name_lower) -                yield _native(name) - -    def __len__(self): -        return len(set(name.lower() for name, _ in self.fields)) - -    # __hash__ = object.__hash__ - -    def _index(self, name): -        name = name.lower() -        for i, field in enumerate(self.fields): -            if field[0].lower() == name: -                return i -        return None - -    def __eq__(self, other): -        if isinstance(other, Headers): -            return self.fields == other.fields -        return False - -    def __ne__(self, other): -        return not self.__eq__(other) - -    @_always_byte_args +        for x in super(Headers, self).__iter__(): +            yield _native(x) +      def get_all(self, name):          """          Like :py:meth:`get`, but does not fold multiple headers into a single one.          This is useful for Set-Cookie headers, which do not support folding. -          See also: https://tools.ietf.org/html/rfc7230#section-3.2.2          """ -        name_lower = name.lower() -        values = [_native(value) for n, value in self.fields if n.lower() == name_lower] -        return values +        name = _always_bytes(name) +        return [ +            _native(x) for x in +            super(Headers, self).get_all(name) +        ] -    @_always_byte_args      def set_all(self, name, values):          """          Explicitly set multiple headers for the given key.          See: :py:meth:`get_all`          """ -        values = map(_always_bytes, values)  # _always_byte_args does not fix lists -        if name in self: -            del self[name] -        self.fields.extend( -            [name, value] for value in values -        ) - -    def get_state(self): -        return tuple(tuple(field) for field in self.fields) - -    def set_state(self, state): -        self.fields = [list(field) for field in state] +        name = _always_bytes(name) +        values = [_always_bytes(x) for x in values] +        return super(Headers, self).set_all(name, values) -    @classmethod -    def from_state(cls, state): -        return cls([list(field) for field in state]) +    def insert(self, index, key, value): +        key = _always_bytes(key) +        value = _always_bytes(value) +        super(Headers, self).insert(index, key, value) -    @_always_byte_args      def replace(self, pattern, repl, flags=0):          """          Replaces a regular expression pattern with repl in each "name: value" @@ -211,6 +155,8 @@ class Headers(MutableMapping, Serializable):          Returns:              The number of replacements made.          """ +        pattern = _always_bytes(pattern) +        repl = _always_bytes(repl)          pattern = re.compile(pattern, flags)          replacements = 0  | 
