mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-28 09:51:36 +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
|
||||
|
||||
@@ -23,7 +23,7 @@ __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)",
|
||||
"Louis Nyffenegger",
|
||||
"Alex Yu"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "0.10.3"
|
||||
__version__ = '0.11.3'
|
||||
|
||||
import re
|
||||
import sys
|
||||
@@ -146,6 +146,7 @@ if sys.version_info < (2,4):
|
||||
seq.sort()
|
||||
return seq
|
||||
|
||||
|
||||
# Python 2.3 support
|
||||
def HTTPResponse__getheaders(self):
|
||||
"""Return list of (header, value) tuples."""
|
||||
@@ -223,6 +224,7 @@ except ImportError:
|
||||
# Which headers are hop-by-hop headers by default
|
||||
HOP_BY_HOP = ['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 'upgrade']
|
||||
|
||||
|
||||
def _get_end2end_headers(response):
|
||||
hopbyhop = list(HOP_BY_HOP)
|
||||
hopbyhop.extend([x.strip() for x in response.get('connection', '').split(',')])
|
||||
@@ -230,6 +232,7 @@ def _get_end2end_headers(response):
|
||||
|
||||
URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
|
||||
|
||||
|
||||
def parse_uri(uri):
|
||||
"""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()
|
||||
return (groups[1], groups[3], groups[4], groups[6], groups[8])
|
||||
|
||||
|
||||
def urlnorm(uri):
|
||||
(scheme, authority, path, query, fragment) = parse_uri(uri)
|
||||
if not scheme or not authority:
|
||||
@@ -258,6 +262,7 @@ def urlnorm(uri):
|
||||
re_url_scheme = re.compile(r'^\w+://')
|
||||
re_slash = re.compile(r'[?/:|]+')
|
||||
|
||||
|
||||
def safename(filename):
|
||||
"""Return a filename suitable for the cache.
|
||||
|
||||
@@ -286,12 +291,15 @@ def safename(filename):
|
||||
return ",".join((filename, filemd5))
|
||||
|
||||
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
|
||||
|
||||
|
||||
def _normalize_headers(headers):
|
||||
return dict([ (key.lower(), NORMALIZE_SPACE.sub(value, ' ').strip()) for (key, value) in headers.iteritems()])
|
||||
|
||||
|
||||
def _parse_cache_control(headers):
|
||||
retval = {}
|
||||
if headers.has_key('cache-control'):
|
||||
if 'cache-control' in headers:
|
||||
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_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
|
||||
per auth_scheme."""
|
||||
retval = {}
|
||||
if headers.has_key(headername):
|
||||
if headername in headers:
|
||||
try:
|
||||
|
||||
authenticate = headers[headername].strip()
|
||||
@@ -344,6 +352,7 @@ def _parse_www_authenticate(headers, headername='www-authenticate'):
|
||||
return retval
|
||||
|
||||
|
||||
# TODO: add current time as _entry_disposition argument to avoid sleep in tests
|
||||
def _entry_disposition(response_headers, request_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_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"
|
||||
if 'cache-control' not in request_headers:
|
||||
request_headers['cache-control'] = 'no-cache'
|
||||
elif cc.has_key('no-cache'):
|
||||
elif 'no-cache' in cc:
|
||||
retval = "TRANSPARENT"
|
||||
elif cc_response.has_key('no-cache'):
|
||||
elif 'no-cache' in cc_response:
|
||||
retval = "STALE"
|
||||
elif cc.has_key('only-if-cached'):
|
||||
elif 'only-if-cached' in cc:
|
||||
retval = "FRESH"
|
||||
elif response_headers.has_key('date'):
|
||||
elif 'date' in response_headers:
|
||||
date = calendar.timegm(email.Utils.parsedate_tz(response_headers['date']))
|
||||
now = time.time()
|
||||
current_age = max(0, now - date)
|
||||
if cc_response.has_key('max-age'):
|
||||
if 'max-age' in cc_response:
|
||||
try:
|
||||
freshness_lifetime = int(cc_response['max-age'])
|
||||
except ValueError:
|
||||
freshness_lifetime = 0
|
||||
elif response_headers.has_key('expires'):
|
||||
elif 'expires' in response_headers:
|
||||
expires = email.Utils.parsedate_tz(response_headers['expires'])
|
||||
if None == expires:
|
||||
freshness_lifetime = 0
|
||||
@@ -403,12 +412,12 @@ def _entry_disposition(response_headers, request_headers):
|
||||
freshness_lifetime = max(0, calendar.timegm(expires) - date)
|
||||
else:
|
||||
freshness_lifetime = 0
|
||||
if cc.has_key('max-age'):
|
||||
if 'max-age' in cc:
|
||||
try:
|
||||
freshness_lifetime = int(cc['max-age'])
|
||||
except ValueError:
|
||||
freshness_lifetime = 0
|
||||
if cc.has_key('min-fresh'):
|
||||
if 'min-fresh' in cc:
|
||||
try:
|
||||
min_fresh = int(cc['min-fresh'])
|
||||
except ValueError:
|
||||
@@ -418,6 +427,7 @@ def _entry_disposition(response_headers, request_headers):
|
||||
retval = "FRESH"
|
||||
return retval
|
||||
|
||||
|
||||
def _decompressContent(response, new_content):
|
||||
content = new_content
|
||||
try:
|
||||
@@ -426,21 +436,22 @@ def _decompressContent(response, new_content):
|
||||
if encoding == 'gzip':
|
||||
content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read()
|
||||
if encoding == 'deflate':
|
||||
content = zlib.decompress(content)
|
||||
content = zlib.decompress(content, -zlib.MAX_WBITS)
|
||||
response['content-length'] = str(len(content))
|
||||
# Record the historical presence of the encoding in a way the won't interfere.
|
||||
response['-content-encoding'] = response['content-encoding']
|
||||
del response['content-encoding']
|
||||
except IOError:
|
||||
except (IOError, zlib.error):
|
||||
content = ""
|
||||
raise FailedToDecompressContent(_("Content purported to be compressed with %s but failed to decompress.") % response.get('content-encoding'), response, content)
|
||||
return content
|
||||
|
||||
|
||||
def _updateCache(request_headers, response_headers, content, cache, cachekey):
|
||||
if cachekey:
|
||||
cc = _parse_cache_control(request_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)
|
||||
else:
|
||||
info = email.Message.Message()
|
||||
@@ -522,7 +533,6 @@ class Authentication(object):
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class BasicAuthentication(Authentication):
|
||||
def __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
|
||||
|
||||
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', {})
|
||||
if 'true' == challenge.get('stale'):
|
||||
self.challenge['nonce'] = challenge['nonce']
|
||||
@@ -585,7 +595,7 @@ class DigestAuthentication(Authentication):
|
||||
else:
|
||||
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['nc'] = 1
|
||||
return False
|
||||
@@ -675,6 +685,7 @@ class WsseAuthentication(Authentication):
|
||||
cnonce,
|
||||
iso_now)
|
||||
|
||||
|
||||
class GoogleLoginAuthentication(Authentication):
|
||||
def __init__(self, credentials, host, request_uri, headers, response, content, http):
|
||||
from urllib import urlencode
|
||||
@@ -714,12 +725,13 @@ AUTH_SCHEME_CLASSES = {
|
||||
|
||||
AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"]
|
||||
|
||||
|
||||
class FileCache(object):
|
||||
"""Uses a local directory as a store for cached files.
|
||||
Not really safe to use if multiple threads or processes are going to
|
||||
be running on the same cache.
|
||||
"""
|
||||
def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior
|
||||
def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior
|
||||
self.cache = cache
|
||||
self.safe = safe
|
||||
if not os.path.exists(cache):
|
||||
@@ -747,6 +759,7 @@ class FileCache(object):
|
||||
if os.path.exists(cacheFullPath):
|
||||
os.remove(cacheFullPath)
|
||||
|
||||
|
||||
class Credentials(object):
|
||||
def __init__(self):
|
||||
self.credentials = []
|
||||
@@ -762,14 +775,17 @@ class Credentials(object):
|
||||
if cdomain == "" or domain == cdomain:
|
||||
yield (name, password)
|
||||
|
||||
|
||||
class KeyCerts(Credentials):
|
||||
"""Identical to Credentials except that
|
||||
name/password are mapped to key/cert."""
|
||||
pass
|
||||
|
||||
|
||||
class AllHosts(object):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyInfo(object):
|
||||
"""Collect information required to use a proxy."""
|
||||
bypass_hosts = ()
|
||||
@@ -822,12 +838,20 @@ class ProxyInfo(object):
|
||||
if self.bypass_hosts is AllHosts:
|
||||
return True
|
||||
|
||||
bypass = False
|
||||
for domain in self.bypass_hosts:
|
||||
if hostname.endswith(domain):
|
||||
bypass = True
|
||||
hostname = '.' + hostname.lstrip('.')
|
||||
for skip_name in self.bypass_hosts:
|
||||
# *.suffix
|
||||
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'):
|
||||
@@ -841,20 +865,10 @@ def proxy_info_from_environment(method='http'):
|
||||
url = os.environ.get(env_var, os.environ.get(env_var.upper()))
|
||||
if not url:
|
||||
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
|
||||
return pi
|
||||
|
||||
def proxy_info_from_url(url, method='http'):
|
||||
def proxy_info_from_url(url, method='http', noproxy=None):
|
||||
"""
|
||||
Construct a ProxyInfo from a URL (such as http_proxy env var)
|
||||
"""
|
||||
@@ -880,8 +894,8 @@ def proxy_info_from_url(url, method='http'):
|
||||
else:
|
||||
port = dict(https=443, http=80)[method]
|
||||
|
||||
proxy_type = 3 # socks.PROXY_TYPE_HTTP
|
||||
return ProxyInfo(
|
||||
proxy_type = 3 # socks.PROXY_TYPE_HTTP
|
||||
pi = ProxyInfo(
|
||||
proxy_type = proxy_type,
|
||||
proxy_host = host,
|
||||
proxy_port = port,
|
||||
@@ -890,6 +904,20 @@ def proxy_info_from_url(url, method='http'):
|
||||
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):
|
||||
"""
|
||||
@@ -939,16 +967,18 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
|
||||
self.sock.settimeout(self.timeout)
|
||||
# End of difference from httplib.
|
||||
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))
|
||||
|
||||
self.sock.connect((self.host, self.port) + sa[2:])
|
||||
except socket.error, msg:
|
||||
print("proxy: %s ************" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)))
|
||||
if use_proxy:
|
||||
self.sock.connect((self.host, self.port) + sa[2:])
|
||||
else:
|
||||
self.sock.connect(sa)
|
||||
except socket.error as msg:
|
||||
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:
|
||||
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:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
@@ -957,6 +987,7 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
|
||||
if not self.sock:
|
||||
raise socket.error, msg
|
||||
|
||||
|
||||
class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
||||
"""
|
||||
This class allows communication via SSL.
|
||||
@@ -1062,15 +1093,19 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
||||
|
||||
if has_timeout(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(
|
||||
sock, self.key_file, self.cert_file,
|
||||
self.disable_ssl_certificate_validation, self.ca_certs,
|
||||
self.ssl_version, self.host)
|
||||
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))
|
||||
print("proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)))
|
||||
if not self.disable_ssl_certificate_validation:
|
||||
cert = self.sock.getpeercert()
|
||||
hostname = self.host.split(':', 0)[0]
|
||||
@@ -1078,7 +1113,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
||||
raise CertificateHostnameMismatch(
|
||||
'Server presented certificate that does not match '
|
||||
'host %s: %s' % (hostname, cert), hostname, cert)
|
||||
except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch), e:
|
||||
except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch) as e:
|
||||
if sock:
|
||||
sock.close()
|
||||
if self.sock:
|
||||
@@ -1094,11 +1129,11 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
|
||||
raise
|
||||
except (socket.timeout, socket.gaierror):
|
||||
raise
|
||||
except socket.error, msg:
|
||||
except socket.error as msg:
|
||||
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:
|
||||
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:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
@@ -1290,7 +1325,7 @@ class Http(object):
|
||||
challenges = _parse_www_authenticate(response, 'www-authenticate')
|
||||
for cred in self.credentials.iter(host):
|
||||
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)
|
||||
|
||||
def add_credentials(self, name, password, domain=""):
|
||||
@@ -1326,13 +1361,13 @@ class Http(object):
|
||||
except ssl_SSLError:
|
||||
conn.close()
|
||||
raise
|
||||
except socket.error, e:
|
||||
except socket.error as e:
|
||||
err = 0
|
||||
if hasattr(e, 'args'):
|
||||
err = getattr(e, 'args')[0]
|
||||
else:
|
||||
err = e.errno
|
||||
if err == errno.ECONNREFUSED: # Connection refused
|
||||
if err == errno.ECONNREFUSED: # Connection refused
|
||||
raise
|
||||
if err in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES:
|
||||
continue # retry on potentially transient socket errors
|
||||
@@ -1418,29 +1453,29 @@ class Http(object):
|
||||
# Pick out the location header and basically start from the beginning
|
||||
# remembering first to strip the ETag header and decrement our 'depth'
|
||||
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)
|
||||
# Fix-up relative redirects (which violate an RFC 2616 MUST)
|
||||
if response.has_key('location'):
|
||||
if 'location' in response:
|
||||
location = response['location']
|
||||
(scheme, authority, path, query, fragment) = parse_uri(location)
|
||||
if authority == None:
|
||||
response['location'] = urlparse.urljoin(absolute_uri, location)
|
||||
if response.status == 301 and method in ["GET", "HEAD"]:
|
||||
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
|
||||
_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']
|
||||
if headers.has_key('if-modified-since'):
|
||||
if 'if-modified-since' in headers:
|
||||
del headers['if-modified-since']
|
||||
if 'authorization' in headers and not self.forward_authorization_headers:
|
||||
del headers['authorization']
|
||||
if response.has_key('location'):
|
||||
if 'location' in response:
|
||||
location = response['location']
|
||||
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
|
||||
redirect_method = method
|
||||
if response.status in [302, 303]:
|
||||
@@ -1455,7 +1490,7 @@ class Http(object):
|
||||
raise RedirectLimit("Redirected more times than rediection_limit allows.", response, content)
|
||||
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
|
||||
if not response.has_key('content-location'):
|
||||
if 'content-location' not in response:
|
||||
response['content-location'] = absolute_uri
|
||||
_updateCache(headers, response, content, self.cache, cachekey)
|
||||
|
||||
@@ -1497,7 +1532,7 @@ class Http(object):
|
||||
else:
|
||||
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__
|
||||
|
||||
uri = iri2uri(uri)
|
||||
@@ -1568,7 +1603,7 @@ class Http(object):
|
||||
else:
|
||||
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/
|
||||
headers['if-match'] = info['etag']
|
||||
|
||||
@@ -1589,7 +1624,7 @@ class Http(object):
|
||||
break
|
||||
|
||||
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.
|
||||
if redirections <= 0:
|
||||
raise RedirectLimit("Redirected more times than rediection_limit allows.", {}, "")
|
||||
@@ -1619,9 +1654,9 @@ class Http(object):
|
||||
return (response, content)
|
||||
|
||||
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']
|
||||
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']
|
||||
elif entry_disposition == "TRANSPARENT":
|
||||
pass
|
||||
@@ -1651,13 +1686,13 @@ class Http(object):
|
||||
content = new_content
|
||||
else:
|
||||
cc = _parse_cache_control(headers)
|
||||
if cc.has_key('only-if-cached'):
|
||||
if 'only-if-cached' in cc:
|
||||
info['status'] = '504'
|
||||
response = Response(info)
|
||||
content = ""
|
||||
else:
|
||||
(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 isinstance(e, HttpLib2ErrorWithResponse):
|
||||
response = e.response
|
||||
@@ -1738,9 +1773,8 @@ class Response(dict):
|
||||
self.status = int(self.get('status', self.status))
|
||||
self.reason = self.get('reason', self.reason)
|
||||
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == 'dict':
|
||||
return self
|
||||
else:
|
||||
raise AttributeError, name
|
||||
raise AttributeError(name)
|
||||
|
||||
@@ -2165,3 +2165,32 @@ R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
|
||||
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
|
||||
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
|
||||
-----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]:
|
||||
# Resolve remotely
|
||||
ipaddr = None
|
||||
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
|
||||
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr.encode()
|
||||
else:
|
||||
# Resolve locally
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import print_function
|
||||
import unittest
|
||||
import errno
|
||||
import os
|
||||
@@ -52,7 +53,7 @@ class FunctionalProxyHttpTest(unittest.TestCase):
|
||||
# TODO use subprocess.check_call when 2.4 is dropped
|
||||
ret = subprocess.call(['tinyproxy', '-c', self.conffile])
|
||||
self.assertEqual(0, ret)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise nose.SkipTest('tinyproxy not available')
|
||||
raise
|
||||
@@ -62,11 +63,11 @@ class FunctionalProxyHttpTest(unittest.TestCase):
|
||||
try:
|
||||
pid = int(open(self.pidfile).read())
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
if e.errno == errno.ESRCH:
|
||||
print '\n\n\nTinyProxy Failed to start, log follows:'
|
||||
print open(self.logfile).read()
|
||||
print 'end tinyproxy log\n\n\n'
|
||||
print('\n\n\nTinyProxy Failed to start, log follows:')
|
||||
print(open(self.logfile).read())
|
||||
print('end tinyproxy log\n\n\n')
|
||||
raise
|
||||
map(os.unlink, (self.pidfile,
|
||||
self.logfile,
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import print_function
|
||||
import BaseHTTPServer
|
||||
import logging
|
||||
import os.path
|
||||
import unittest
|
||||
import ssl
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import httplib2
|
||||
|
||||
from httplib2.test import miniserver
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KeepAliveHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
"""
|
||||
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
|
||||
logger.info(s, *args)
|
||||
|
||||
|
||||
class HttpsContextTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if sys.version_info < (2, 7, 9):
|
||||
return
|
||||
if hasattr(self, "skipTest"):
|
||||
self.skipTest("SSLContext requires Python 2.7.9")
|
||||
else:
|
||||
return
|
||||
|
||||
self.httpd, self.port = miniserver.start_server(
|
||||
KeepAliveHandler, True)
|
||||
self.ca_certs_path = os.path.join(os.path.dirname(__file__), 'server.pem')
|
||||
self.httpd, self.port = miniserver.start_server(KeepAliveHandler, True)
|
||||
|
||||
def tearDown(self):
|
||||
self.httpd.shutdown()
|
||||
|
||||
def testHttpsContext(self):
|
||||
if sys.version_info < (2, 7, 9):
|
||||
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'))
|
||||
client = httplib2.Http(ca_certs=self.ca_certs_path)
|
||||
|
||||
# Establish connection to local server
|
||||
client.request('https://localhost:%d/' % (self.port))
|
||||
@@ -61,6 +60,27 @@ class HttpsContextTest(unittest.TestCase):
|
||||
self.assertIsInstance(conn.sock.context, ssl.SSLContext)
|
||||
self.assertTrue(conn.sock.context.check_hostname)
|
||||
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.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