aboutsummaryrefslogtreecommitdiffstats
path: root/netlib
diff options
context:
space:
mode:
Diffstat (limited to 'netlib')
-rw-r--r--netlib/http/http1/protocol.py14
-rw-r--r--netlib/http/semantics.py43
-rw-r--r--netlib/odict.py3
-rw-r--r--netlib/tutils.py4
-rw-r--r--netlib/utils.py56
5 files changed, 87 insertions, 33 deletions
diff --git a/netlib/http/http1/protocol.py b/netlib/http/http1/protocol.py
index 2e85a762..31e9cc85 100644
--- a/netlib/http/http1/protocol.py
+++ b/netlib/http/http1/protocol.py
@@ -360,20 +360,6 @@ class HTTP1Protocol(semantics.ProtocolMixin):
@classmethod
- def request_preamble(self, method, resource, http_major="1", http_minor="1"):
- return '%s %s HTTP/%s.%s' % (
- method, resource, http_major, http_minor
- )
-
-
- @classmethod
- def response_preamble(self, code, message=None, http_major="1", http_minor="1"):
- if message is None:
- message = status_codes.RESPONSES.get(code)
- return 'HTTP/%s.%s %s %s' % (http_major, http_minor, code, message)
-
-
- @classmethod
def has_chunked_encoding(self, headers):
return "chunked" in [
i.lower() for i in utils.get_header_tokens(headers, "transfer-encoding")
diff --git a/netlib/http/semantics.py b/netlib/http/semantics.py
index e7ae2b5f..974fe6e6 100644
--- a/netlib/http/semantics.py
+++ b/netlib/http/semantics.py
@@ -7,7 +7,7 @@ import urllib
import urlparse
from .. import utils, odict
-from . import cookies
+from . import cookies, exceptions
from netlib import utils, encoding
HDR_FORM_URLENCODED = "application/x-www-form-urlencoded"
@@ -18,10 +18,10 @@ CONTENT_MISSING = 0
class ProtocolMixin(object):
- def read_request(self):
+ def read_request(self, *args, **kwargs): # pragma: no cover
raise NotImplemented
- def read_response(self):
+ def read_response(self, *args, **kwargs): # pragma: no cover
raise NotImplemented
def assemble(self, message):
@@ -32,14 +32,23 @@ class ProtocolMixin(object):
else:
raise ValueError("HTTP message not supported.")
- def assemble_request(self, request):
+ def assemble_request(self, request): # pragma: no cover
raise NotImplemented
- def assemble_response(self, response):
+ def assemble_response(self, response): # pragma: no cover
raise NotImplemented
class Request(object):
+ # This list is adopted legacy code.
+ # We probably don't need to strip off keep-alive.
+ _headers_to_strip_off = [
+ 'Proxy-Connection',
+ 'Keep-Alive',
+ 'Connection',
+ 'Transfer-Encoding',
+ 'Upgrade',
+ ]
def __init__(
self,
@@ -71,7 +80,6 @@ class Request(object):
self.timestamp_start = timestamp_start
self.timestamp_end = timestamp_end
-
def __eq__(self, other):
try:
self_d = [self.__dict__[k] for k in self.__dict__ if k not in ('timestamp_start', 'timestamp_end')]
@@ -114,7 +122,7 @@ class Request(object):
self.httpversion[1],
)
else:
- raise http.HttpError(400, "Invalid request form")
+ raise exceptions.HttpError(400, "Invalid request form")
def anticache(self):
"""
@@ -143,7 +151,7 @@ class Request(object):
if self.headers["accept-encoding"]:
self.headers["accept-encoding"] = [
', '.join(
- e for e in encoding.ENCODINGS if e in self.headers["accept-encoding"][0])]
+ e for e in encoding.ENCODINGS if e in self.headers.get_first("accept-encoding"))]
def update_host_header(self):
"""
@@ -317,12 +325,12 @@ class Request(object):
self.scheme, self.host, self.port, self.path = parts
@property
- def content(self):
+ def content(self): # pragma: no cover
# TODO: remove deprecated getter
return self.body
@content.setter
- def content(self, content):
+ def content(self, content): # pragma: no cover
# TODO: remove deprecated setter
self.body = content
@@ -343,6 +351,11 @@ class EmptyRequest(Request):
class Response(object):
+ _headers_to_strip_off = [
+ 'Proxy-Connection',
+ 'Alternate-Protocol',
+ 'Alt-Svc',
+ ]
def __init__(
self,
@@ -368,7 +381,6 @@ class Response(object):
self.timestamp_start = timestamp_start
self.timestamp_end = timestamp_end
-
def __eq__(self, other):
try:
self_d = [self.__dict__[k] for k in self.__dict__ if k not in ('timestamp_start', 'timestamp_end')]
@@ -393,7 +405,6 @@ class Response(object):
size=size
)
-
def get_cookies(self):
"""
Get the contents of all Set-Cookie headers.
@@ -430,21 +441,21 @@ class Response(object):
self.headers["Set-Cookie"] = values
@property
- def content(self):
+ def content(self): # pragma: no cover
# TODO: remove deprecated getter
return self.body
@content.setter
- def content(self, content):
+ def content(self, content): # pragma: no cover
# TODO: remove deprecated setter
self.body = content
@property
- def code(self):
+ def code(self): # pragma: no cover
# TODO: remove deprecated getter
return self.status_code
@code.setter
- def code(self, code):
+ def code(self, code): # pragma: no cover
# TODO: remove deprecated setter
self.status_code = code
diff --git a/netlib/odict.py b/netlib/odict.py
index d02de08d..11d5d52a 100644
--- a/netlib/odict.py
+++ b/netlib/odict.py
@@ -91,8 +91,9 @@ class ODict(object):
self.lst = self._filter_lst(k, self.lst)
def __contains__(self, k):
+ k = self._kconv(k)
for i in self.lst:
- if self._kconv(i[0]) == self._kconv(k):
+ if self._kconv(i[0]) == k:
return True
return False
diff --git a/netlib/tutils.py b/netlib/tutils.py
index 5018b9e8..3c471d0d 100644
--- a/netlib/tutils.py
+++ b/netlib/tutils.py
@@ -119,7 +119,7 @@ def tresp(content="message"):
"OK",
headers,
content,
- time.time(),
- time.time(),
+ timestamp_start=time.time(),
+ timestamp_end=time.time(),
)
return resp
diff --git a/netlib/utils.py b/netlib/utils.py
index 35ea0ec7..2dfcafc6 100644
--- a/netlib/utils.py
+++ b/netlib/utils.py
@@ -4,6 +4,7 @@ import cgi
import urllib
import urlparse
import string
+import re
def isascii(s):
@@ -239,3 +240,58 @@ def urldecode(s):
Takes a urlencoded string and returns a list of (key, value) tuples.
"""
return cgi.parse_qsl(s, keep_blank_values=True)
+
+
+def parse_content_type(c):
+ """
+ A simple parser for content-type values. Returns a (type, subtype,
+ parameters) tuple, where type and subtype are strings, and parameters
+ is a dict. If the string could not be parsed, return None.
+
+ E.g. the following string:
+
+ text/html; charset=UTF-8
+
+ Returns:
+
+ ("text", "html", {"charset": "UTF-8"})
+ """
+ parts = c.split(";", 1)
+ ts = parts[0].split("/", 1)
+ if len(ts) != 2:
+ return None
+ d = {}
+ if len(parts) == 2:
+ for i in parts[1].split(";"):
+ clause = i.split("=", 1)
+ if len(clause) == 2:
+ d[clause[0].strip()] = clause[1].strip()
+ return ts[0].lower(), ts[1].lower(), d
+
+
+def multipartdecode(hdrs, content):
+ """
+ Takes a multipart boundary encoded string and returns list of (key, value) tuples.
+ """
+ v = hdrs.get_first("content-type")
+ if v:
+ v = parse_content_type(v)
+ if not v:
+ return []
+ boundary = v[2].get("boundary")
+ if not boundary:
+ return []
+
+ rx = re.compile(r'\bname="([^"]+)"')
+ r = []
+
+ for i in content.split("--" + boundary):
+ parts = i.splitlines()
+ if len(parts) > 1 and parts[0][0:2] != "--":
+ match = rx.search(parts[1])
+ if match:
+ key = match.group(1)
+ value = "".join(parts[3 + parts[2:].index(""):])
+ r.append((key, value))
+ return r
+ return []