aboutsummaryrefslogtreecommitdiffstats
path: root/netlib
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2015-09-05 13:26:36 +0200
committerMaximilian Hils <git@maximilianhils.com>2015-09-05 13:26:36 +0200
commit5f97701958a283fca7188623c3cb4a313456b82c (patch)
tree00c9416b58d5dee873f91799abac721677f48ac3 /netlib
parent97d224752473911f3efdba01b173dc9481e40b50 (diff)
downloadmitmproxy-5f97701958a283fca7188623c3cb4a313456b82c.tar.gz
mitmproxy-5f97701958a283fca7188623c3cb4a313456b82c.tar.bz2
mitmproxy-5f97701958a283fca7188623c3cb4a313456b82c.zip
add new headers class
Diffstat (limited to 'netlib')
-rw-r--r--netlib/http/semantics.py130
1 files changed, 129 insertions, 1 deletions
diff --git a/netlib/http/semantics.py b/netlib/http/semantics.py
index 2b960483..162cdbf5 100644
--- a/netlib/http/semantics.py
+++ b/netlib/http/semantics.py
@@ -1,4 +1,5 @@
from __future__ import (absolute_import, print_function, division)
+import UserDict
import urllib
import urlparse
@@ -12,8 +13,135 @@ HDR_FORM_MULTIPART = "multipart/form-data"
CONTENT_MISSING = 0
-class ProtocolMixin(object):
+class Headers(UserDict.DictMixin):
+ """
+ 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.
+
+ Example:
+
+ .. code-block:: python
+
+ # Create header from a list of (header_name, header_value) tuples
+ >>> h = Headers([
+ ["Host","example.com"],
+ ["Accept","text/html"],
+ ["accept","application/xml"]
+ ])
+
+ # Headers mostly behave like a normal dict.
+ >>> h["Host"]
+ "example.com"
+
+ # HTTP Headers are case insensitive
+ >>> h["host"]
+ "example.com"
+
+ # Multiple headers are folded into a single header as per RFC7230
+ >>> h["Accept"]
+ "text/html, application/xml"
+
+ # Setting a header removes all existing headers with the same name.
+ >>> h["Accept"] = "application/text"
+ >>> h["Accept"]
+ "application/text"
+
+ # str(h) returns a HTTP1 header block.
+ >>> print(h)
+ Host: example.com
+ Accept: application/text
+
+ # For full control, the raw header lines can be accessed
+ >>> h.lines
+
+ # Headers can also be crated from keyword arguments
+ >>> h = Headers(host="example.com", content_type="application/xml")
+
+ Caveats:
+ For use with the "Set-Cookie" header, see :py:meth:`get_all`.
+ """
+
+ def __init__(self, lines=None, **headers):
+ """
+ For convenience, underscores in header names will be transformed to dashes.
+ This behaviour does not extend to other methods.
+
+ If ``**headers`` contains multiple keys that have equal ``.lower()``s,
+ the behavior is undefined.
+ """
+ self.lines = lines or []
+
+ # content_type -> content-type
+ headers = {k.replace("_", "-"): v for k, v in headers.iteritems()}
+ self.update(headers)
+
+ def __str__(self):
+ return "\r\n".join(": ".join(line) for line in self.lines)
+
+ def __getitem__(self, key):
+ values = self.get_all(key)
+ if not values:
+ raise KeyError(key)
+ else:
+ return ", ".join(values)
+ def __setitem__(self, key, value):
+ idx = self._index(key)
+
+ # To please the human eye, we insert at the same position the first existing header occured.
+ if idx is not None:
+ del self[key]
+ self.lines.insert(idx, [key, value])
+ else:
+ self.lines.append([key, value])
+
+ def __delitem__(self, key):
+ key = key.lower()
+ self.lines = [
+ line for line in self.lines
+ if key != line[0].lower()
+ ]
+
+ def _index(self, key):
+ key = key.lower()
+ for i, line in enumerate(self):
+ if line[0].lower() == key:
+ return i
+ return None
+
+ def keys(self):
+ return list(set(line[0] for line in self.lines))
+
+ def __eq__(self, other):
+ return self.lines == other.lines
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def get_all(self, key, default=None):
+ """
+ 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
+ """
+ key = key.lower()
+ values = [line[1] for line in self.lines if line[0].lower() == key]
+ return values or default
+
+ def set_all(self, key, values):
+ """
+ Explicitly set multiple headers for the given key.
+ See: :py:meth:`get_all`
+ """
+ if key in self:
+ del self[key]
+ self.lines.extend(
+ [key, value] for value in values
+ )
+
+
+class ProtocolMixin(object):
def read_request(self, *args, **kwargs): # pragma: no cover
raise NotImplementedError