mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-03 20:31:35 +00:00
httplib2 0.11.3
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
from __future__ import generators
|
from __future__ import print_function
|
||||||
"""
|
"""
|
||||||
httplib2
|
httplib2
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)",
|
|||||||
"Louis Nyffenegger",
|
"Louis Nyffenegger",
|
||||||
"Alex Yu"]
|
"Alex Yu"]
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
__version__ = "0.10.3"
|
__version__ = '0.11.3'
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -146,6 +146,7 @@ if sys.version_info < (2,4):
|
|||||||
seq.sort()
|
seq.sort()
|
||||||
return seq
|
return seq
|
||||||
|
|
||||||
|
|
||||||
# Python 2.3 support
|
# Python 2.3 support
|
||||||
def HTTPResponse__getheaders(self):
|
def HTTPResponse__getheaders(self):
|
||||||
"""Return list of (header, value) tuples."""
|
"""Return list of (header, value) tuples."""
|
||||||
@@ -223,6 +224,7 @@ except ImportError:
|
|||||||
# Which headers are hop-by-hop headers by default
|
# Which headers are hop-by-hop headers by default
|
||||||
HOP_BY_HOP = ['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 'upgrade']
|
HOP_BY_HOP = ['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 'upgrade']
|
||||||
|
|
||||||
|
|
||||||
def _get_end2end_headers(response):
|
def _get_end2end_headers(response):
|
||||||
hopbyhop = list(HOP_BY_HOP)
|
hopbyhop = list(HOP_BY_HOP)
|
||||||
hopbyhop.extend([x.strip() for x in response.get('connection', '').split(',')])
|
hopbyhop.extend([x.strip() for x in response.get('connection', '').split(',')])
|
||||||
@@ -230,6 +232,7 @@ def _get_end2end_headers(response):
|
|||||||
|
|
||||||
URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
|
URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
|
||||||
|
|
||||||
|
|
||||||
def parse_uri(uri):
|
def parse_uri(uri):
|
||||||
"""Parses a URI using the regex given in Appendix B of RFC 3986.
|
"""Parses a URI using the regex given in Appendix B of RFC 3986.
|
||||||
|
|
||||||
@@ -238,6 +241,7 @@ def parse_uri(uri):
|
|||||||
groups = URI.match(uri).groups()
|
groups = URI.match(uri).groups()
|
||||||
return (groups[1], groups[3], groups[4], groups[6], groups[8])
|
return (groups[1], groups[3], groups[4], groups[6], groups[8])
|
||||||
|
|
||||||
|
|
||||||
def urlnorm(uri):
|
def urlnorm(uri):
|
||||||
(scheme, authority, path, query, fragment) = parse_uri(uri)
|
(scheme, authority, path, query, fragment) = parse_uri(uri)
|
||||||
if not scheme or not authority:
|
if not scheme or not authority:
|
||||||
@@ -258,6 +262,7 @@ def urlnorm(uri):
|
|||||||
re_url_scheme = re.compile(r'^\w+://')
|
re_url_scheme = re.compile(r'^\w+://')
|
||||||
re_slash = re.compile(r'[?/:|]+')
|
re_slash = re.compile(r'[?/:|]+')
|
||||||
|
|
||||||
|
|
||||||
def safename(filename):
|
def safename(filename):
|
||||||
"""Return a filename suitable for the cache.
|
"""Return a filename suitable for the cache.
|
||||||
|
|
||||||
@@ -286,12 +291,15 @@ def safename(filename):
|
|||||||
return ",".join((filename, filemd5))
|
return ",".join((filename, filemd5))
|
||||||
|
|
||||||
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
|
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
|
||||||
|
|
||||||
|
|
||||||
def _normalize_headers(headers):
|
def _normalize_headers(headers):
|
||||||
return dict([ (key.lower(), NORMALIZE_SPACE.sub(value, ' ').strip()) for (key, value) in headers.iteritems()])
|
return dict([ (key.lower(), NORMALIZE_SPACE.sub(value, ' ').strip()) for (key, value) in headers.iteritems()])
|
||||||
|
|
||||||
|
|
||||||
def _parse_cache_control(headers):
|
def _parse_cache_control(headers):
|
||||||
retval = {}
|
retval = {}
|
||||||
if headers.has_key('cache-control'):
|
if 'cache-control' in headers:
|
||||||
parts = headers['cache-control'].split(',')
|
parts = headers['cache-control'].split(',')
|
||||||
parts_with_args = [tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")]
|
parts_with_args = [tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")]
|
||||||
parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")]
|
parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")]
|
||||||
@@ -316,7 +324,7 @@ def _parse_www_authenticate(headers, headername='www-authenticate'):
|
|||||||
"""Returns a dictionary of dictionaries, one dict
|
"""Returns a dictionary of dictionaries, one dict
|
||||||
per auth_scheme."""
|
per auth_scheme."""
|
||||||
retval = {}
|
retval = {}
|
||||||
if headers.has_key(headername):
|
if headername in headers:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
authenticate = headers[headername].strip()
|
authenticate = headers[headername].strip()
|
||||||
@@ -344,6 +352,7 @@ def _parse_www_authenticate(headers, headername='www-authenticate'):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: add current time as _entry_disposition argument to avoid sleep in tests
|
||||||
def _entry_disposition(response_headers, request_headers):
|
def _entry_disposition(response_headers, request_headers):
|
||||||
"""Determine freshness from the Date, Expires and Cache-Control headers.
|
"""Determine freshness from the Date, Expires and Cache-Control headers.
|
||||||
|
|
||||||
@@ -376,26 +385,26 @@ def _entry_disposition(response_headers, request_headers):
|
|||||||
cc = _parse_cache_control(request_headers)
|
cc = _parse_cache_control(request_headers)
|
||||||
cc_response = _parse_cache_control(response_headers)
|
cc_response = _parse_cache_control(response_headers)
|
||||||
|
|
||||||
if request_headers.has_key('pragma') and request_headers['pragma'].lower().find('no-cache') != -1:
|
if 'pragma' in request_headers and request_headers['pragma'].lower().find('no-cache') != -1:
|
||||||
retval = "TRANSPARENT"
|
retval = "TRANSPARENT"
|
||||||
if 'cache-control' not in request_headers:
|
if 'cache-control' not in request_headers:
|
||||||
request_headers['cache-control'] = 'no-cache'
|
request_headers['cache-control'] = 'no-cache'
|
||||||
elif cc.has_key('no-cache'):
|
elif 'no-cache' in cc:
|
||||||
retval = "TRANSPARENT"
|
retval = "TRANSPARENT"
|
||||||
elif cc_response.has_key('no-cache'):
|
elif 'no-cache' in cc_response:
|
||||||
retval = "STALE"
|
retval = "STALE"
|
||||||
elif cc.has_key('only-if-cached'):
|
elif 'only-if-cached' in cc:
|
||||||
retval = "FRESH"
|
retval = "FRESH"
|
||||||
elif response_headers.has_key('date'):
|
elif 'date' in response_headers:
|
||||||
date = calendar.timegm(email.Utils.parsedate_tz(response_headers['date']))
|
date = calendar.timegm(email.Utils.parsedate_tz(response_headers['date']))
|
||||||
now = time.time()
|
now = time.time()
|
||||||
current_age = max(0, now - date)
|
current_age = max(0, now - date)
|
||||||
if cc_response.has_key('max-age'):
|
if 'max-age' in cc_response:
|
||||||
try:
|
try:
|
||||||
freshness_lifetime = int(cc_response['max-age'])
|
freshness_lifetime = int(cc_response['max-age'])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
freshness_lifetime = 0
|
freshness_lifetime = 0
|
||||||
elif response_headers.has_key('expires'):
|
elif 'expires' in response_headers:
|
||||||
expires = email.Utils.parsedate_tz(response_headers['expires'])
|
expires = email.Utils.parsedate_tz(response_headers['expires'])
|
||||||
if None == expires:
|
if None == expires:
|
||||||
freshness_lifetime = 0
|
freshness_lifetime = 0
|
||||||
@@ -403,12 +412,12 @@ def _entry_disposition(response_headers, request_headers):
|
|||||||
freshness_lifetime = max(0, calendar.timegm(expires) - date)
|
freshness_lifetime = max(0, calendar.timegm(expires) - date)
|
||||||
else:
|
else:
|
||||||
freshness_lifetime = 0
|
freshness_lifetime = 0
|
||||||
if cc.has_key('max-age'):
|
if 'max-age' in cc:
|
||||||
try:
|
try:
|
||||||
freshness_lifetime = int(cc['max-age'])
|
freshness_lifetime = int(cc['max-age'])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
freshness_lifetime = 0
|
freshness_lifetime = 0
|
||||||
if cc.has_key('min-fresh'):
|
if 'min-fresh' in cc:
|
||||||
try:
|
try:
|
||||||
min_fresh = int(cc['min-fresh'])
|
min_fresh = int(cc['min-fresh'])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -418,6 +427,7 @@ def _entry_disposition(response_headers, request_headers):
|
|||||||
retval = "FRESH"
|
retval = "FRESH"
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
def _decompressContent(response, new_content):
|
def _decompressContent(response, new_content):
|
||||||
content = new_content
|
content = new_content
|
||||||
try:
|
try:
|
||||||
@@ -426,21 +436,22 @@ def _decompressContent(response, new_content):
|
|||||||
if encoding == 'gzip':
|
if encoding == 'gzip':
|
||||||
content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read()
|
content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read()
|
||||||
if encoding == 'deflate':
|
if encoding == 'deflate':
|
||||||
content = zlib.decompress(content)
|
content = zlib.decompress(content, -zlib.MAX_WBITS)
|
||||||
response['content-length'] = str(len(content))
|
response['content-length'] = str(len(content))
|
||||||
# Record the historical presence of the encoding in a way the won't interfere.
|
# Record the historical presence of the encoding in a way the won't interfere.
|
||||||
response['-content-encoding'] = response['content-encoding']
|
response['-content-encoding'] = response['content-encoding']
|
||||||
del response['content-encoding']
|
del response['content-encoding']
|
||||||
except IOError:
|
except (IOError, zlib.error):
|
||||||
content = ""
|
content = ""
|
||||||
raise FailedToDecompressContent(_("Content purported to be compressed with %s but failed to decompress.") % response.get('content-encoding'), response, content)
|
raise FailedToDecompressContent(_("Content purported to be compressed with %s but failed to decompress.") % response.get('content-encoding'), response, content)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def _updateCache(request_headers, response_headers, content, cache, cachekey):
|
def _updateCache(request_headers, response_headers, content, cache, cachekey):
|
||||||
if cachekey:
|
if cachekey:
|
||||||
cc = _parse_cache_control(request_headers)
|
cc = _parse_cache_control(request_headers)
|
||||||
cc_response = _parse_cache_control(response_headers)
|
cc_response = _parse_cache_control(response_headers)
|
||||||
if cc.has_key('no-store') or cc_response.has_key('no-store'):
|
if 'no-store' in cc or 'no-store' in cc_response:
|
||||||
cache.delete(cachekey)
|
cache.delete(cachekey)
|
||||||
else:
|
else:
|
||||||
info = email.Message.Message()
|
info = email.Message.Message()
|
||||||
@@ -522,7 +533,6 @@ class Authentication(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BasicAuthentication(Authentication):
|
class BasicAuthentication(Authentication):
|
||||||
def __init__(self, credentials, host, request_uri, headers, response, content, http):
|
def __init__(self, credentials, host, request_uri, headers, response, content, http):
|
||||||
Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
|
Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
|
||||||
@@ -576,7 +586,7 @@ class DigestAuthentication(Authentication):
|
|||||||
self.challenge['nc'] += 1
|
self.challenge['nc'] += 1
|
||||||
|
|
||||||
def response(self, response, content):
|
def response(self, response, content):
|
||||||
if not response.has_key('authentication-info'):
|
if 'authentication-info' not in response:
|
||||||
challenge = _parse_www_authenticate(response, 'www-authenticate').get('digest', {})
|
challenge = _parse_www_authenticate(response, 'www-authenticate').get('digest', {})
|
||||||
if 'true' == challenge.get('stale'):
|
if 'true' == challenge.get('stale'):
|
||||||
self.challenge['nonce'] = challenge['nonce']
|
self.challenge['nonce'] = challenge['nonce']
|
||||||
@@ -585,7 +595,7 @@ class DigestAuthentication(Authentication):
|
|||||||
else:
|
else:
|
||||||
updated_challenge = _parse_www_authenticate(response, 'authentication-info').get('digest', {})
|
updated_challenge = _parse_www_authenticate(response, 'authentication-info').get('digest', {})
|
||||||
|
|
||||||
if updated_challenge.has_key('nextnonce'):
|
if 'nextnonce' in updated_challenge:
|
||||||
self.challenge['nonce'] = updated_challenge['nextnonce']
|
self.challenge['nonce'] = updated_challenge['nextnonce']
|
||||||
self.challenge['nc'] = 1
|
self.challenge['nc'] = 1
|
||||||
return False
|
return False
|
||||||
@@ -675,6 +685,7 @@ class WsseAuthentication(Authentication):
|
|||||||
cnonce,
|
cnonce,
|
||||||
iso_now)
|
iso_now)
|
||||||
|
|
||||||
|
|
||||||
class GoogleLoginAuthentication(Authentication):
|
class GoogleLoginAuthentication(Authentication):
|
||||||
def __init__(self, credentials, host, request_uri, headers, response, content, http):
|
def __init__(self, credentials, host, request_uri, headers, response, content, http):
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
@@ -714,6 +725,7 @@ AUTH_SCHEME_CLASSES = {
|
|||||||
|
|
||||||
AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"]
|
AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"]
|
||||||
|
|
||||||
|
|
||||||
class FileCache(object):
|
class FileCache(object):
|
||||||
"""Uses a local directory as a store for cached files.
|
"""Uses a local directory as a store for cached files.
|
||||||
Not really safe to use if multiple threads or processes are going to
|
Not really safe to use if multiple threads or processes are going to
|
||||||
@@ -747,6 +759,7 @@ class FileCache(object):
|
|||||||
if os.path.exists(cacheFullPath):
|
if os.path.exists(cacheFullPath):
|
||||||
os.remove(cacheFullPath)
|
os.remove(cacheFullPath)
|
||||||
|
|
||||||
|
|
||||||
class Credentials(object):
|
class Credentials(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.credentials = []
|
self.credentials = []
|
||||||
@@ -762,14 +775,17 @@ class Credentials(object):
|
|||||||
if cdomain == "" or domain == cdomain:
|
if cdomain == "" or domain == cdomain:
|
||||||
yield (name, password)
|
yield (name, password)
|
||||||
|
|
||||||
|
|
||||||
class KeyCerts(Credentials):
|
class KeyCerts(Credentials):
|
||||||
"""Identical to Credentials except that
|
"""Identical to Credentials except that
|
||||||
name/password are mapped to key/cert."""
|
name/password are mapped to key/cert."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AllHosts(object):
|
class AllHosts(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProxyInfo(object):
|
class ProxyInfo(object):
|
||||||
"""Collect information required to use a proxy."""
|
"""Collect information required to use a proxy."""
|
||||||
bypass_hosts = ()
|
bypass_hosts = ()
|
||||||
@@ -822,12 +838,20 @@ class ProxyInfo(object):
|
|||||||
if self.bypass_hosts is AllHosts:
|
if self.bypass_hosts is AllHosts:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
bypass = False
|
hostname = '.' + hostname.lstrip('.')
|
||||||
for domain in self.bypass_hosts:
|
for skip_name in self.bypass_hosts:
|
||||||
if hostname.endswith(domain):
|
# *.suffix
|
||||||
bypass = True
|
if skip_name.startswith('.') and hostname.endswith(skip_name):
|
||||||
|
return True
|
||||||
|
# exact match
|
||||||
|
if hostname == '.' + skip_name:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
return bypass
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
'<ProxyInfo type={p.proxy_type} host:port={p.proxy_host}:{p.proxy_port} rdns={p.proxy_rdns}' +
|
||||||
|
' user={p.proxy_user} headers={p.proxy_headers}>').format(p=self)
|
||||||
|
|
||||||
|
|
||||||
def proxy_info_from_environment(method='http'):
|
def proxy_info_from_environment(method='http'):
|
||||||
@@ -841,20 +865,10 @@ def proxy_info_from_environment(method='http'):
|
|||||||
url = os.environ.get(env_var, os.environ.get(env_var.upper()))
|
url = os.environ.get(env_var, os.environ.get(env_var.upper()))
|
||||||
if not url:
|
if not url:
|
||||||
return
|
return
|
||||||
pi = proxy_info_from_url(url, method)
|
return proxy_info_from_url(url, method, None)
|
||||||
|
|
||||||
no_proxy = os.environ.get('no_proxy', os.environ.get('NO_PROXY', ''))
|
|
||||||
bypass_hosts = []
|
|
||||||
if no_proxy:
|
|
||||||
bypass_hosts = no_proxy.split(',')
|
|
||||||
# special case, no_proxy=* means all hosts bypassed
|
|
||||||
if no_proxy == '*':
|
|
||||||
bypass_hosts = AllHosts
|
|
||||||
|
|
||||||
pi.bypass_hosts = bypass_hosts
|
def proxy_info_from_url(url, method='http', noproxy=None):
|
||||||
return pi
|
|
||||||
|
|
||||||
def proxy_info_from_url(url, method='http'):
|
|
||||||
"""
|
"""
|
||||||
Construct a ProxyInfo from a URL (such as http_proxy env var)
|
Construct a ProxyInfo from a URL (such as http_proxy env var)
|
||||||
"""
|
"""
|
||||||
@@ -881,7 +895,7 @@ def proxy_info_from_url(url, method='http'):
|
|||||||
port = dict(https=443, http=80)[method]
|
port = dict(https=443, http=80)[method]
|
||||||
|
|
||||||
proxy_type = 3 # socks.PROXY_TYPE_HTTP
|
proxy_type = 3 # socks.PROXY_TYPE_HTTP
|
||||||
return ProxyInfo(
|
pi = ProxyInfo(
|
||||||
proxy_type = proxy_type,
|
proxy_type = proxy_type,
|
||||||
proxy_host = host,
|
proxy_host = host,
|
||||||
proxy_port = port,
|
proxy_port = port,
|
||||||
@@ -890,6 +904,20 @@ def proxy_info_from_url(url, method='http'):
|
|||||||
proxy_headers = None,
|
proxy_headers = None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bypass_hosts = []
|
||||||
|
# If not given an explicit noproxy value, respect values in env vars.
|
||||||
|
if noproxy is None:
|
||||||
|
noproxy = os.environ.get('no_proxy', os.environ.get('NO_PROXY', ''))
|
||||||
|
# Special case: A single '*' character means all hosts should be bypassed.
|
||||||
|
if noproxy == '*':
|
||||||
|
bypass_hosts = AllHosts
|
||||||
|
elif noproxy.strip():
|
||||||
|
bypass_hosts = noproxy.split(',')
|
||||||
|
bypass_hosts = filter(bool, bypass_hosts) # To exclude empty string.
|
||||||
|
|
||||||
|
pi.bypass_hosts = bypass_hosts
|
||||||
|
return pi
|
||||||
|
|
||||||
|
|
||||||
class HTTPConnectionWithTimeout(httplib.HTTPConnection):
|
class HTTPConnectionWithTimeout(httplib.HTTPConnection):
|
||||||
"""
|
"""
|
||||||
@@ -939,16 +967,18 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
|
|||||||
self.sock.settimeout(self.timeout)
|
self.sock.settimeout(self.timeout)
|
||||||
# End of difference from httplib.
|
# End of difference from httplib.
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
print "connect: (%s, %s) ************" % (self.host, self.port)
|
print("connect: (%s, %s) ************" % (self.host, self.port))
|
||||||
|
if use_proxy:
|
||||||
|
print("proxy: %s ************" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)))
|
||||||
if use_proxy:
|
if use_proxy:
|
||||||
print "proxy: %s ************" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers))
|
|
||||||
|
|
||||||
self.sock.connect((self.host, self.port) + sa[2:])
|
self.sock.connect((self.host, self.port) + sa[2:])
|
||||||
except socket.error, msg:
|
else:
|
||||||
|
self.sock.connect(sa)
|
||||||
|
except socket.error as msg:
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
print "connect fail: (%s, %s)" % (self.host, self.port)
|
print("connect fail: (%s, %s)" % (self.host, self.port))
|
||||||
if use_proxy:
|
if use_proxy:
|
||||||
print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers))
|
print("proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)))
|
||||||
if self.sock:
|
if self.sock:
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
self.sock = None
|
self.sock = None
|
||||||
@@ -957,6 +987,7 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
|
|||||||
if not self.sock:
|
if not self.sock:
|
||||||
raise socket.error, msg
|
raise socket.error, msg
|
||||||
|
|
||||||
|
|
||||||
class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
||||||
"""
|
"""
|
||||||
This class allows communication via SSL.
|
This class allows communication via SSL.
|
||||||
@@ -1062,15 +1093,19 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
|||||||
|
|
||||||
if has_timeout(self.timeout):
|
if has_timeout(self.timeout):
|
||||||
sock.settimeout(self.timeout)
|
sock.settimeout(self.timeout)
|
||||||
sock.connect((self.host, self.port))
|
|
||||||
|
if use_proxy:
|
||||||
|
sock.connect((self.host, self.port) + sockaddr[:2])
|
||||||
|
else:
|
||||||
|
sock.connect(sockaddr)
|
||||||
self.sock =_ssl_wrap_socket(
|
self.sock =_ssl_wrap_socket(
|
||||||
sock, self.key_file, self.cert_file,
|
sock, self.key_file, self.cert_file,
|
||||||
self.disable_ssl_certificate_validation, self.ca_certs,
|
self.disable_ssl_certificate_validation, self.ca_certs,
|
||||||
self.ssl_version, self.host)
|
self.ssl_version, self.host)
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
print "connect: (%s, %s)" % (self.host, self.port)
|
print("connect: (%s, %s)" % (self.host, self.port))
|
||||||
if use_proxy:
|
if use_proxy:
|
||||||
print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers))
|
print("proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)))
|
||||||
if not self.disable_ssl_certificate_validation:
|
if not self.disable_ssl_certificate_validation:
|
||||||
cert = self.sock.getpeercert()
|
cert = self.sock.getpeercert()
|
||||||
hostname = self.host.split(':', 0)[0]
|
hostname = self.host.split(':', 0)[0]
|
||||||
@@ -1078,7 +1113,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
|||||||
raise CertificateHostnameMismatch(
|
raise CertificateHostnameMismatch(
|
||||||
'Server presented certificate that does not match '
|
'Server presented certificate that does not match '
|
||||||
'host %s: %s' % (hostname, cert), hostname, cert)
|
'host %s: %s' % (hostname, cert), hostname, cert)
|
||||||
except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch), e:
|
except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch) as e:
|
||||||
if sock:
|
if sock:
|
||||||
sock.close()
|
sock.close()
|
||||||
if self.sock:
|
if self.sock:
|
||||||
@@ -1094,11 +1129,11 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
|||||||
raise
|
raise
|
||||||
except (socket.timeout, socket.gaierror):
|
except (socket.timeout, socket.gaierror):
|
||||||
raise
|
raise
|
||||||
except socket.error, msg:
|
except socket.error as msg:
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
print "connect fail: (%s, %s)" % (self.host, self.port)
|
print("connect fail: (%s, %s)" % (self.host, self.port))
|
||||||
if use_proxy:
|
if use_proxy:
|
||||||
print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers))
|
print("proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)))
|
||||||
if self.sock:
|
if self.sock:
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
self.sock = None
|
self.sock = None
|
||||||
@@ -1290,7 +1325,7 @@ class Http(object):
|
|||||||
challenges = _parse_www_authenticate(response, 'www-authenticate')
|
challenges = _parse_www_authenticate(response, 'www-authenticate')
|
||||||
for cred in self.credentials.iter(host):
|
for cred in self.credentials.iter(host):
|
||||||
for scheme in AUTH_SCHEME_ORDER:
|
for scheme in AUTH_SCHEME_ORDER:
|
||||||
if challenges.has_key(scheme):
|
if scheme in challenges:
|
||||||
yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self)
|
yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self)
|
||||||
|
|
||||||
def add_credentials(self, name, password, domain=""):
|
def add_credentials(self, name, password, domain=""):
|
||||||
@@ -1326,7 +1361,7 @@ class Http(object):
|
|||||||
except ssl_SSLError:
|
except ssl_SSLError:
|
||||||
conn.close()
|
conn.close()
|
||||||
raise
|
raise
|
||||||
except socket.error, e:
|
except socket.error as e:
|
||||||
err = 0
|
err = 0
|
||||||
if hasattr(e, 'args'):
|
if hasattr(e, 'args'):
|
||||||
err = getattr(e, 'args')[0]
|
err = getattr(e, 'args')[0]
|
||||||
@@ -1418,29 +1453,29 @@ class Http(object):
|
|||||||
# Pick out the location header and basically start from the beginning
|
# Pick out the location header and basically start from the beginning
|
||||||
# remembering first to strip the ETag header and decrement our 'depth'
|
# remembering first to strip the ETag header and decrement our 'depth'
|
||||||
if redirections:
|
if redirections:
|
||||||
if not response.has_key('location') and response.status != 300:
|
if 'location' not in response and response.status != 300:
|
||||||
raise RedirectMissingLocation( _("Redirected but the response is missing a Location: header."), response, content)
|
raise RedirectMissingLocation( _("Redirected but the response is missing a Location: header."), response, content)
|
||||||
# Fix-up relative redirects (which violate an RFC 2616 MUST)
|
# Fix-up relative redirects (which violate an RFC 2616 MUST)
|
||||||
if response.has_key('location'):
|
if 'location' in response:
|
||||||
location = response['location']
|
location = response['location']
|
||||||
(scheme, authority, path, query, fragment) = parse_uri(location)
|
(scheme, authority, path, query, fragment) = parse_uri(location)
|
||||||
if authority == None:
|
if authority == None:
|
||||||
response['location'] = urlparse.urljoin(absolute_uri, location)
|
response['location'] = urlparse.urljoin(absolute_uri, location)
|
||||||
if response.status == 301 and method in ["GET", "HEAD"]:
|
if response.status == 301 and method in ["GET", "HEAD"]:
|
||||||
response['-x-permanent-redirect-url'] = response['location']
|
response['-x-permanent-redirect-url'] = response['location']
|
||||||
if not response.has_key('content-location'):
|
if 'content-location' not in response:
|
||||||
response['content-location'] = absolute_uri
|
response['content-location'] = absolute_uri
|
||||||
_updateCache(headers, response, content, self.cache, cachekey)
|
_updateCache(headers, response, content, self.cache, cachekey)
|
||||||
if headers.has_key('if-none-match'):
|
if 'if-none-match' in headers:
|
||||||
del headers['if-none-match']
|
del headers['if-none-match']
|
||||||
if headers.has_key('if-modified-since'):
|
if 'if-modified-since' in headers:
|
||||||
del headers['if-modified-since']
|
del headers['if-modified-since']
|
||||||
if 'authorization' in headers and not self.forward_authorization_headers:
|
if 'authorization' in headers and not self.forward_authorization_headers:
|
||||||
del headers['authorization']
|
del headers['authorization']
|
||||||
if response.has_key('location'):
|
if 'location' in response:
|
||||||
location = response['location']
|
location = response['location']
|
||||||
old_response = copy.deepcopy(response)
|
old_response = copy.deepcopy(response)
|
||||||
if not old_response.has_key('content-location'):
|
if 'content-location' not in old_response:
|
||||||
old_response['content-location'] = absolute_uri
|
old_response['content-location'] = absolute_uri
|
||||||
redirect_method = method
|
redirect_method = method
|
||||||
if response.status in [302, 303]:
|
if response.status in [302, 303]:
|
||||||
@@ -1455,7 +1490,7 @@ class Http(object):
|
|||||||
raise RedirectLimit("Redirected more times than rediection_limit allows.", response, content)
|
raise RedirectLimit("Redirected more times than rediection_limit allows.", response, content)
|
||||||
elif response.status in [200, 203] and method in ["GET", "HEAD"]:
|
elif response.status in [200, 203] and method in ["GET", "HEAD"]:
|
||||||
# Don't cache 206's since we aren't going to handle byte range requests
|
# Don't cache 206's since we aren't going to handle byte range requests
|
||||||
if not response.has_key('content-location'):
|
if 'content-location' not in response:
|
||||||
response['content-location'] = absolute_uri
|
response['content-location'] = absolute_uri
|
||||||
_updateCache(headers, response, content, self.cache, cachekey)
|
_updateCache(headers, response, content, self.cache, cachekey)
|
||||||
|
|
||||||
@@ -1497,7 +1532,7 @@ class Http(object):
|
|||||||
else:
|
else:
|
||||||
headers = self._normalize_headers(headers)
|
headers = self._normalize_headers(headers)
|
||||||
|
|
||||||
if not headers.has_key('user-agent'):
|
if 'user-agent' not in headers:
|
||||||
headers['user-agent'] = "Python-httplib2/%s (gzip)" % __version__
|
headers['user-agent'] = "Python-httplib2/%s (gzip)" % __version__
|
||||||
|
|
||||||
uri = iri2uri(uri)
|
uri = iri2uri(uri)
|
||||||
@@ -1568,7 +1603,7 @@ class Http(object):
|
|||||||
else:
|
else:
|
||||||
cachekey = None
|
cachekey = None
|
||||||
|
|
||||||
if method in self.optimistic_concurrency_methods and self.cache and info.has_key('etag') and not self.ignore_etag and 'if-match' not in headers:
|
if method in self.optimistic_concurrency_methods and self.cache and 'etag' in info and not self.ignore_etag and 'if-match' not in headers:
|
||||||
# http://www.w3.org/1999/04/Editing/
|
# http://www.w3.org/1999/04/Editing/
|
||||||
headers['if-match'] = info['etag']
|
headers['if-match'] = info['etag']
|
||||||
|
|
||||||
@@ -1589,7 +1624,7 @@ class Http(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if cached_value and method in ["GET", "HEAD"] and self.cache and 'range' not in headers:
|
if cached_value and method in ["GET", "HEAD"] and self.cache and 'range' not in headers:
|
||||||
if info.has_key('-x-permanent-redirect-url'):
|
if '-x-permanent-redirect-url' in info:
|
||||||
# Should cached permanent redirects be counted in our redirection count? For now, yes.
|
# Should cached permanent redirects be counted in our redirection count? For now, yes.
|
||||||
if redirections <= 0:
|
if redirections <= 0:
|
||||||
raise RedirectLimit("Redirected more times than rediection_limit allows.", {}, "")
|
raise RedirectLimit("Redirected more times than rediection_limit allows.", {}, "")
|
||||||
@@ -1619,9 +1654,9 @@ class Http(object):
|
|||||||
return (response, content)
|
return (response, content)
|
||||||
|
|
||||||
if entry_disposition == "STALE":
|
if entry_disposition == "STALE":
|
||||||
if info.has_key('etag') and not self.ignore_etag and not 'if-none-match' in headers:
|
if 'etag' in info and not self.ignore_etag and not 'if-none-match' in headers:
|
||||||
headers['if-none-match'] = info['etag']
|
headers['if-none-match'] = info['etag']
|
||||||
if info.has_key('last-modified') and not 'last-modified' in headers:
|
if 'last-modified' in info and not 'last-modified' in headers:
|
||||||
headers['if-modified-since'] = info['last-modified']
|
headers['if-modified-since'] = info['last-modified']
|
||||||
elif entry_disposition == "TRANSPARENT":
|
elif entry_disposition == "TRANSPARENT":
|
||||||
pass
|
pass
|
||||||
@@ -1651,13 +1686,13 @@ class Http(object):
|
|||||||
content = new_content
|
content = new_content
|
||||||
else:
|
else:
|
||||||
cc = _parse_cache_control(headers)
|
cc = _parse_cache_control(headers)
|
||||||
if cc.has_key('only-if-cached'):
|
if 'only-if-cached' in cc:
|
||||||
info['status'] = '504'
|
info['status'] = '504'
|
||||||
response = Response(info)
|
response = Response(info)
|
||||||
content = ""
|
content = ""
|
||||||
else:
|
else:
|
||||||
(response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
|
(response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
if self.force_exception_to_status_code:
|
if self.force_exception_to_status_code:
|
||||||
if isinstance(e, HttpLib2ErrorWithResponse):
|
if isinstance(e, HttpLib2ErrorWithResponse):
|
||||||
response = e.response
|
response = e.response
|
||||||
@@ -1738,9 +1773,8 @@ class Response(dict):
|
|||||||
self.status = int(self.get('status', self.status))
|
self.status = int(self.get('status', self.status))
|
||||||
self.reason = self.get('reason', self.reason)
|
self.reason = self.get('reason', self.reason)
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name == 'dict':
|
if name == 'dict':
|
||||||
return self
|
return self
|
||||||
else:
|
else:
|
||||||
raise AttributeError, name
|
raise AttributeError(name)
|
||||||
|
|||||||
@@ -2165,3 +2165,32 @@ R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
|
|||||||
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
|
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
|
||||||
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
|
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
# Issuer: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US
|
||||||
|
# Subject: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US
|
||||||
|
# Serial: 33af1e6a711a9a0bb2864b11d09fae5
|
||||||
|
# MD5 Fingerprint: E4:A6:8A:C8:54:AC:52:42:46:0A:FD:72:48:1B:2A:44
|
||||||
|
# SHA1 Fingerprint: DF:3C:24:F9:BF:D6:66:76:1B:26:80:73:FE:06:D1:CC:8D:4F:82:A4
|
||||||
|
# SHA256 Fingerprint: CB:3C:CB:B7:60:31:E5:E0:13:8F:8D:D3:9A:23:F9:DE:47:FF:C3:5E:43:C1:14:4C:EA:27:D4:6A:5A:B1:CB:5F
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
|
||||||
|
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||||
|
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
|
||||||
|
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
|
||||||
|
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
||||||
|
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
|
||||||
|
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
|
||||||
|
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
|
||||||
|
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
|
||||||
|
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
|
||||||
|
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
|
||||||
|
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
|
||||||
|
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
|
||||||
|
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
|
||||||
|
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
|
||||||
|
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
|
||||||
|
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
|
||||||
|
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
|
||||||
|
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
|
||||||
|
MrY=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ class socksocket(socket.socket):
|
|||||||
if self.__proxy[3]:
|
if self.__proxy[3]:
|
||||||
# Resolve remotely
|
# Resolve remotely
|
||||||
ipaddr = None
|
ipaddr = None
|
||||||
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
|
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr.encode()
|
||||||
else:
|
else:
|
||||||
# Resolve locally
|
# Resolve locally
|
||||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import print_function
|
||||||
import unittest
|
import unittest
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
@@ -52,7 +53,7 @@ class FunctionalProxyHttpTest(unittest.TestCase):
|
|||||||
# TODO use subprocess.check_call when 2.4 is dropped
|
# TODO use subprocess.check_call when 2.4 is dropped
|
||||||
ret = subprocess.call(['tinyproxy', '-c', self.conffile])
|
ret = subprocess.call(['tinyproxy', '-c', self.conffile])
|
||||||
self.assertEqual(0, ret)
|
self.assertEqual(0, ret)
|
||||||
except OSError, e:
|
except OSError as e:
|
||||||
if e.errno == errno.ENOENT:
|
if e.errno == errno.ENOENT:
|
||||||
raise nose.SkipTest('tinyproxy not available')
|
raise nose.SkipTest('tinyproxy not available')
|
||||||
raise
|
raise
|
||||||
@@ -62,11 +63,11 @@ class FunctionalProxyHttpTest(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
pid = int(open(self.pidfile).read())
|
pid = int(open(self.pidfile).read())
|
||||||
os.kill(pid, signal.SIGTERM)
|
os.kill(pid, signal.SIGTERM)
|
||||||
except OSError, e:
|
except OSError as e:
|
||||||
if e.errno == errno.ESRCH:
|
if e.errno == errno.ESRCH:
|
||||||
print '\n\n\nTinyProxy Failed to start, log follows:'
|
print('\n\n\nTinyProxy Failed to start, log follows:')
|
||||||
print open(self.logfile).read()
|
print(open(self.logfile).read())
|
||||||
print 'end tinyproxy log\n\n\n'
|
print('end tinyproxy log\n\n\n')
|
||||||
raise
|
raise
|
||||||
map(os.unlink, (self.pidfile,
|
map(os.unlink, (self.pidfile,
|
||||||
self.logfile,
|
self.logfile,
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
#!/usr/bin/python2
|
#!/usr/bin/env python2
|
||||||
|
from __future__ import print_function
|
||||||
import BaseHTTPServer
|
import BaseHTTPServer
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
import unittest
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
import httplib2
|
import httplib2
|
||||||
|
|
||||||
from httplib2.test import miniserver
|
from httplib2.test import miniserver
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class KeepAliveHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
class KeepAliveHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
"""
|
"""
|
||||||
Request handler that keeps the HTTP connection open, so that the test can
|
Request handler that keeps the HTTP connection open, so that the test can
|
||||||
@@ -28,27 +31,23 @@ class KeepAliveHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
# output via logging so nose can catch it
|
# output via logging so nose can catch it
|
||||||
logger.info(s, *args)
|
logger.info(s, *args)
|
||||||
|
|
||||||
|
|
||||||
class HttpsContextTest(unittest.TestCase):
|
class HttpsContextTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if sys.version_info < (2, 7, 9):
|
if sys.version_info < (2, 7, 9):
|
||||||
|
if hasattr(self, "skipTest"):
|
||||||
|
self.skipTest("SSLContext requires Python 2.7.9")
|
||||||
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.httpd, self.port = miniserver.start_server(
|
self.ca_certs_path = os.path.join(os.path.dirname(__file__), 'server.pem')
|
||||||
KeepAliveHandler, True)
|
self.httpd, self.port = miniserver.start_server(KeepAliveHandler, True)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.httpd.shutdown()
|
self.httpd.shutdown()
|
||||||
|
|
||||||
def testHttpsContext(self):
|
def testHttpsContext(self):
|
||||||
if sys.version_info < (2, 7, 9):
|
client = httplib2.Http(ca_certs=self.ca_certs_path)
|
||||||
if hasattr(unittest, "skipTest"):
|
|
||||||
self.skipTest("SSLContext requires Python 2.7.9")# Python 2.7.0
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
client = httplib2.Http(
|
|
||||||
ca_certs=os.path.join(os.path.dirname(__file__), 'server.pem'))
|
|
||||||
|
|
||||||
# Establish connection to local server
|
# Establish connection to local server
|
||||||
client.request('https://localhost:%d/' % (self.port))
|
client.request('https://localhost:%d/' % (self.port))
|
||||||
@@ -61,6 +60,27 @@ class HttpsContextTest(unittest.TestCase):
|
|||||||
self.assertIsInstance(conn.sock.context, ssl.SSLContext)
|
self.assertIsInstance(conn.sock.context, ssl.SSLContext)
|
||||||
self.assertTrue(conn.sock.context.check_hostname)
|
self.assertTrue(conn.sock.context.check_hostname)
|
||||||
self.assertEqual(conn.sock.server_hostname, 'localhost')
|
self.assertEqual(conn.sock.server_hostname, 'localhost')
|
||||||
self.assertEqual(conn.sock.context.check_hostname, True)
|
|
||||||
self.assertEqual(conn.sock.context.verify_mode, ssl.CERT_REQUIRED)
|
self.assertEqual(conn.sock.context.verify_mode, ssl.CERT_REQUIRED)
|
||||||
self.assertEqual(conn.sock.context.protocol, ssl.PROTOCOL_SSLv23)
|
self.assertEqual(conn.sock.context.protocol, ssl.PROTOCOL_SSLv23)
|
||||||
|
|
||||||
|
def test_ssl_hostname_mismatch_repeat(self):
|
||||||
|
# https://github.com/httplib2/httplib2/issues/5
|
||||||
|
|
||||||
|
# FIXME(temoto): as of 2017-01-05 this is only a reference code, not useful test.
|
||||||
|
# Because it doesn't provoke described error on my machine.
|
||||||
|
# Instead `SSLContext.wrap_socket` raises `ssl.CertificateError`
|
||||||
|
# which was also added to original patch.
|
||||||
|
|
||||||
|
# url host is intentionally different, we provoke ssl hostname mismatch error
|
||||||
|
url = 'https://127.0.0.1:%d/' % (self.port,)
|
||||||
|
http = httplib2.Http(ca_certs=self.ca_certs_path, proxy_info=None)
|
||||||
|
|
||||||
|
def once():
|
||||||
|
try:
|
||||||
|
http.request(url)
|
||||||
|
assert False, 'expected certificate hostname mismatch error'
|
||||||
|
except Exception as e:
|
||||||
|
print('%s errno=%s' % (repr(e), getattr(e, 'errno', None)))
|
||||||
|
|
||||||
|
once()
|
||||||
|
once()
|
||||||
|
|||||||
Reference in New Issue
Block a user