2015-07-14 21:02:14 +00:00
|
|
|
from __future__ import (absolute_import, print_function, division)
|
|
|
|
import binascii
|
|
|
|
import collections
|
|
|
|
import string
|
|
|
|
import sys
|
|
|
|
import urlparse
|
|
|
|
|
|
|
|
from .. import utils
|
|
|
|
|
2015-07-17 07:37:57 +00:00
|
|
|
class Request(object):
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
form_in,
|
|
|
|
method,
|
|
|
|
scheme,
|
|
|
|
host,
|
|
|
|
port,
|
|
|
|
path,
|
|
|
|
httpversion,
|
|
|
|
headers,
|
2015-07-19 15:52:10 +00:00
|
|
|
body,
|
2015-07-17 07:37:57 +00:00
|
|
|
):
|
|
|
|
self.form_in = form_in
|
|
|
|
self.method = method
|
|
|
|
self.scheme = scheme
|
|
|
|
self.host = host
|
|
|
|
self.port = port
|
|
|
|
self.path = path
|
|
|
|
self.httpversion = httpversion
|
|
|
|
self.headers = headers
|
2015-07-19 15:52:10 +00:00
|
|
|
self.body = body
|
2015-07-17 07:37:57 +00:00
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
return self.__dict__ == other.__dict__
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "Request(%s - %s, %s)" % (self.method, self.host, self.path)
|
|
|
|
|
|
|
|
|
2015-07-19 15:52:10 +00:00
|
|
|
class ConnectRequest(Request):
|
|
|
|
def __init__(self, host, port):
|
|
|
|
super(ConnectRequest, self).__init__(
|
|
|
|
form_in="authority",
|
|
|
|
method="CONNECT",
|
|
|
|
scheme="",
|
|
|
|
host=host,
|
|
|
|
port=port,
|
|
|
|
path="",
|
|
|
|
httpversion="",
|
|
|
|
headers="",
|
|
|
|
body="",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2015-07-14 21:02:14 +00:00
|
|
|
class Response(object):
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
httpversion,
|
|
|
|
status_code,
|
|
|
|
msg,
|
|
|
|
headers,
|
2015-07-19 15:52:10 +00:00
|
|
|
body,
|
2015-07-14 21:02:14 +00:00
|
|
|
sslinfo=None,
|
|
|
|
):
|
|
|
|
self.httpversion = httpversion
|
|
|
|
self.status_code = status_code
|
|
|
|
self.msg = msg
|
|
|
|
self.headers = headers
|
2015-07-19 15:52:10 +00:00
|
|
|
self.body = body
|
2015-07-14 21:02:14 +00:00
|
|
|
self.sslinfo = sslinfo
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
return self.__dict__ == other.__dict__
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "Response(%s - %s)" % (self.status_code, self.msg)
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid_port(port):
|
|
|
|
if not 0 <= port <= 65535:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid_host(host):
|
|
|
|
try:
|
|
|
|
host.decode("idna")
|
|
|
|
except ValueError:
|
|
|
|
return False
|
|
|
|
if "\0" in host:
|
|
|
|
return None
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def parse_url(url):
|
|
|
|
"""
|
|
|
|
Returns a (scheme, host, port, path) tuple, or None on error.
|
|
|
|
|
|
|
|
Checks that:
|
|
|
|
port is an integer 0-65535
|
|
|
|
host is a valid IDNA-encoded hostname with no null-bytes
|
|
|
|
path is valid ASCII
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
|
|
|
|
except ValueError:
|
|
|
|
return None
|
|
|
|
if not scheme:
|
|
|
|
return None
|
|
|
|
if '@' in netloc:
|
|
|
|
# FIXME: Consider what to do with the discarded credentials here Most
|
|
|
|
# probably we should extend the signature to return these as a separate
|
|
|
|
# value.
|
|
|
|
_, netloc = string.rsplit(netloc, '@', maxsplit=1)
|
|
|
|
if ':' in netloc:
|
|
|
|
host, port = string.rsplit(netloc, ':', maxsplit=1)
|
|
|
|
try:
|
|
|
|
port = int(port)
|
|
|
|
except ValueError:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
host = netloc
|
|
|
|
if scheme == "https":
|
|
|
|
port = 443
|
|
|
|
else:
|
|
|
|
port = 80
|
|
|
|
path = urlparse.urlunparse(('', '', path, params, query, fragment))
|
|
|
|
if not path.startswith("/"):
|
|
|
|
path = "/" + path
|
|
|
|
if not is_valid_host(host):
|
|
|
|
return None
|
|
|
|
if not utils.isascii(path):
|
|
|
|
return None
|
|
|
|
if not is_valid_port(port):
|
|
|
|
return None
|
|
|
|
return scheme, host, port, path
|
2015-07-15 20:32:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_header_tokens(headers, key):
|
|
|
|
"""
|
|
|
|
Retrieve all tokens for a header key. A number of different headers
|
|
|
|
follow a pattern where each header line can containe comma-separated
|
|
|
|
tokens, and headers can be set multiple times.
|
|
|
|
"""
|
|
|
|
toks = []
|
|
|
|
for i in headers[key]:
|
|
|
|
for j in i.split(","):
|
|
|
|
toks.append(j.strip())
|
|
|
|
return toks
|