aboutsummaryrefslogtreecommitdiffstats
path: root/netlib/http/headers.py
diff options
context:
space:
mode:
Diffstat (limited to 'netlib/http/headers.py')
-rw-r--r--netlib/http/headers.py140
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