Remove unused hotfix file
Follow-up on:
commit cb22afd4fe
Author: Kevin Fenzi <kevin@scrye.com>
Date: Wed May 9 02:00:45 2018 +0000
Look, ask has moved away. I'm sure it will write us back someday...
Signed-off-by: Nils Philippsen <nils@redhat.com>
This commit is contained in:
parent
5958059b47
commit
6d3f844ab2
1 changed files with 0 additions and 430 deletions
|
@ -1,430 +0,0 @@
|
||||||
# -*- test-case-name: openid.test.test_fetchers -*-
|
|
||||||
"""
|
|
||||||
This module contains the HTTP fetcher interface and several implementations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['fetch', 'getDefaultFetcher', 'setDefaultFetcher', 'HTTPResponse',
|
|
||||||
'HTTPFetcher', 'createHTTPFetcher', 'HTTPFetchingError',
|
|
||||||
'HTTPError']
|
|
||||||
|
|
||||||
import urllib2
|
|
||||||
import time
|
|
||||||
import cStringIO
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import openid
|
|
||||||
import openid.urinorm
|
|
||||||
|
|
||||||
# Try to import httplib2 for caching support
|
|
||||||
# http://bitworking.org/projects/httplib2/
|
|
||||||
try:
|
|
||||||
import httplib2
|
|
||||||
except ImportError:
|
|
||||||
# httplib2 not available
|
|
||||||
httplib2 = None
|
|
||||||
|
|
||||||
# try to import pycurl, which will let us use CurlHTTPFetcher
|
|
||||||
try:
|
|
||||||
import pycurl
|
|
||||||
except ImportError:
|
|
||||||
pycurl = None
|
|
||||||
|
|
||||||
USER_AGENT = "python-openid/%s (%s)" % (openid.__version__, sys.platform)
|
|
||||||
MAX_RESPONSE_KB = 1024
|
|
||||||
|
|
||||||
def fetch(url, body=None, headers=None):
|
|
||||||
"""Invoke the fetch method on the default fetcher. Most users
|
|
||||||
should need only this method.
|
|
||||||
|
|
||||||
@raises Exception: any exceptions that may be raised by the default fetcher
|
|
||||||
"""
|
|
||||||
fetcher = getDefaultFetcher()
|
|
||||||
return fetcher.fetch(url, body, headers)
|
|
||||||
|
|
||||||
def createHTTPFetcher():
|
|
||||||
"""Create a default HTTP fetcher instance
|
|
||||||
|
|
||||||
prefers Curl to urllib2."""
|
|
||||||
if pycurl is None:
|
|
||||||
fetcher = Urllib2Fetcher()
|
|
||||||
else:
|
|
||||||
fetcher = CurlHTTPFetcher()
|
|
||||||
|
|
||||||
return fetcher
|
|
||||||
|
|
||||||
# Contains the currently set HTTP fetcher. If it is set to None, the
|
|
||||||
# library will call createHTTPFetcher() to set it. Do not access this
|
|
||||||
# variable outside of this module.
|
|
||||||
_default_fetcher = None
|
|
||||||
|
|
||||||
def getDefaultFetcher():
|
|
||||||
"""Return the default fetcher instance
|
|
||||||
if no fetcher has been set, it will create a default fetcher.
|
|
||||||
|
|
||||||
@return: the default fetcher
|
|
||||||
@rtype: HTTPFetcher
|
|
||||||
"""
|
|
||||||
global _default_fetcher
|
|
||||||
|
|
||||||
if _default_fetcher is None:
|
|
||||||
setDefaultFetcher(createHTTPFetcher())
|
|
||||||
|
|
||||||
return _default_fetcher
|
|
||||||
|
|
||||||
def setDefaultFetcher(fetcher, wrap_exceptions=True):
|
|
||||||
"""Set the default fetcher
|
|
||||||
|
|
||||||
@param fetcher: The fetcher to use as the default HTTP fetcher
|
|
||||||
@type fetcher: HTTPFetcher
|
|
||||||
|
|
||||||
@param wrap_exceptions: Whether to wrap exceptions thrown by the
|
|
||||||
fetcher wil HTTPFetchingError so that they may be caught
|
|
||||||
easier. By default, exceptions will be wrapped. In general,
|
|
||||||
unwrapped fetchers are useful for debugging of fetching errors
|
|
||||||
or if your fetcher raises well-known exceptions that you would
|
|
||||||
like to catch.
|
|
||||||
@type wrap_exceptions: bool
|
|
||||||
"""
|
|
||||||
global _default_fetcher
|
|
||||||
if fetcher is None or not wrap_exceptions:
|
|
||||||
_default_fetcher = fetcher
|
|
||||||
else:
|
|
||||||
_default_fetcher = ExceptionWrappingFetcher(fetcher)
|
|
||||||
|
|
||||||
def usingCurl():
|
|
||||||
"""Whether the currently set HTTP fetcher is a Curl HTTP fetcher."""
|
|
||||||
fetcher = getDefaultFetcher()
|
|
||||||
if isinstance(fetcher, ExceptionWrappingFetcher):
|
|
||||||
fetcher = fetcher.fetcher
|
|
||||||
return isinstance(fetcher, CurlHTTPFetcher)
|
|
||||||
|
|
||||||
class HTTPResponse(object):
|
|
||||||
"""XXX document attributes"""
|
|
||||||
headers = None
|
|
||||||
status = None
|
|
||||||
body = None
|
|
||||||
final_url = None
|
|
||||||
|
|
||||||
def __init__(self, final_url=None, status=None, headers=None, body=None):
|
|
||||||
self.final_url = final_url
|
|
||||||
self.status = status
|
|
||||||
self.headers = headers
|
|
||||||
self.body = body
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s status %s for %s>" % (self.__class__.__name__,
|
|
||||||
self.status,
|
|
||||||
self.final_url)
|
|
||||||
|
|
||||||
class HTTPFetcher(object):
|
|
||||||
"""
|
|
||||||
This class is the interface for openid HTTP fetchers. This
|
|
||||||
interface is only important if you need to write a new fetcher for
|
|
||||||
some reason.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def fetch(self, url, body=None, headers=None):
|
|
||||||
"""
|
|
||||||
This performs an HTTP POST or GET, following redirects along
|
|
||||||
the way. If a body is specified, then the request will be a
|
|
||||||
POST. Otherwise, it will be a GET.
|
|
||||||
|
|
||||||
|
|
||||||
@param headers: HTTP headers to include with the request
|
|
||||||
@type headers: {str:str}
|
|
||||||
|
|
||||||
@return: An object representing the server's HTTP response. If
|
|
||||||
there are network or protocol errors, an exception will be
|
|
||||||
raised. HTTP error responses, like 404 or 500, do not
|
|
||||||
cause exceptions.
|
|
||||||
|
|
||||||
@rtype: L{HTTPResponse}
|
|
||||||
|
|
||||||
@raise Exception: Different implementations will raise
|
|
||||||
different errors based on the underlying HTTP library.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _allowedURL(url):
|
|
||||||
return url.startswith('http://') or url.startswith('https://')
|
|
||||||
|
|
||||||
class HTTPFetchingError(Exception):
|
|
||||||
"""Exception that is wrapped around all exceptions that are raised
|
|
||||||
by the underlying fetcher when using the ExceptionWrappingFetcher
|
|
||||||
|
|
||||||
@ivar why: The exception that caused this exception
|
|
||||||
"""
|
|
||||||
def __init__(self, why=None):
|
|
||||||
Exception.__init__(self, why)
|
|
||||||
self.why = why
|
|
||||||
|
|
||||||
class ExceptionWrappingFetcher(HTTPFetcher):
|
|
||||||
"""Fetcher that wraps another fetcher, causing all exceptions
|
|
||||||
|
|
||||||
@cvar uncaught_exceptions: Exceptions that should be exposed to the
|
|
||||||
user if they are raised by the fetch call
|
|
||||||
"""
|
|
||||||
|
|
||||||
uncaught_exceptions = (SystemExit, KeyboardInterrupt, MemoryError)
|
|
||||||
|
|
||||||
def __init__(self, fetcher):
|
|
||||||
self.fetcher = fetcher
|
|
||||||
|
|
||||||
def fetch(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
return self.fetcher.fetch(*args, **kwargs)
|
|
||||||
except self.uncaught_exceptions:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
exc_cls, exc_inst = sys.exc_info()[:2]
|
|
||||||
if exc_inst is None:
|
|
||||||
# string exceptions
|
|
||||||
exc_inst = exc_cls
|
|
||||||
|
|
||||||
raise HTTPFetchingError(why=exc_inst)
|
|
||||||
|
|
||||||
class Urllib2Fetcher(HTTPFetcher):
|
|
||||||
"""An C{L{HTTPFetcher}} that uses urllib2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Parameterized for the benefit of testing frameworks, see
|
|
||||||
# http://trac.openidenabled.com/trac/ticket/85
|
|
||||||
urlopen = staticmethod(urllib2.urlopen)
|
|
||||||
|
|
||||||
def fetch(self, url, body=None, headers=None):
|
|
||||||
if not _allowedURL(url):
|
|
||||||
raise ValueError('Bad URL scheme: %r' % (url,))
|
|
||||||
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
|
|
||||||
headers.setdefault(
|
|
||||||
'User-Agent',
|
|
||||||
"%s Python-urllib/%s" % (USER_AGENT, urllib2.__version__,))
|
|
||||||
|
|
||||||
req = urllib2.Request(url, data=body, headers=headers)
|
|
||||||
try:
|
|
||||||
f = self.urlopen(req)
|
|
||||||
try:
|
|
||||||
return self._makeResponse(f)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
except urllib2.HTTPError, why:
|
|
||||||
try:
|
|
||||||
return self._makeResponse(why)
|
|
||||||
finally:
|
|
||||||
why.close()
|
|
||||||
|
|
||||||
def _makeResponse(self, urllib2_response):
|
|
||||||
resp = HTTPResponse()
|
|
||||||
resp.body = urllib2_response.read(MAX_RESPONSE_KB * 1024)
|
|
||||||
resp.final_url = urllib2_response.geturl()
|
|
||||||
resp.headers = dict(urllib2_response.info().items())
|
|
||||||
|
|
||||||
if hasattr(urllib2_response, 'code'):
|
|
||||||
resp.status = urllib2_response.code
|
|
||||||
else:
|
|
||||||
resp.status = 200
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
class HTTPError(HTTPFetchingError):
|
|
||||||
"""
|
|
||||||
This exception is raised by the C{L{CurlHTTPFetcher}} when it
|
|
||||||
encounters an exceptional situation fetching a URL.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# XXX: define what we mean by paranoid, and make sure it is.
|
|
||||||
class CurlHTTPFetcher(HTTPFetcher):
|
|
||||||
"""
|
|
||||||
An C{L{HTTPFetcher}} that uses pycurl for fetching.
|
|
||||||
See U{http://pycurl.sourceforge.net/}.
|
|
||||||
"""
|
|
||||||
ALLOWED_TIME = 20 # seconds
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
HTTPFetcher.__init__(self)
|
|
||||||
if pycurl is None:
|
|
||||||
raise RuntimeError('Cannot find pycurl library')
|
|
||||||
|
|
||||||
def _parseHeaders(self, header_file):
|
|
||||||
header_file.seek(0)
|
|
||||||
|
|
||||||
# Remove the status line from the beginning of the input
|
|
||||||
unused_http_status_line = header_file.readline().lower ()
|
|
||||||
while unused_http_status_line.lower().startswith('http/1.1 1'):
|
|
||||||
unused_http_status_line = header_file.readline()
|
|
||||||
unused_http_status_line = header_file.readline()
|
|
||||||
|
|
||||||
lines = [line.strip() for line in header_file]
|
|
||||||
|
|
||||||
# and the blank line from the end
|
|
||||||
empty_line = lines.pop()
|
|
||||||
if empty_line:
|
|
||||||
raise HTTPError("No blank line at end of headers: %r" % (line,))
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
for line in lines:
|
|
||||||
try:
|
|
||||||
name, value = line.split(':', 1)
|
|
||||||
except ValueError:
|
|
||||||
raise HTTPError(
|
|
||||||
"Malformed HTTP header line in response: %r" % (line,))
|
|
||||||
|
|
||||||
value = value.strip()
|
|
||||||
|
|
||||||
# HTTP headers are case-insensitive
|
|
||||||
name = name.lower()
|
|
||||||
headers[name] = value
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def _checkURL(self, url):
|
|
||||||
# XXX: document that this can be overridden to match desired policy
|
|
||||||
# XXX: make sure url is well-formed and routeable
|
|
||||||
return _allowedURL(url)
|
|
||||||
|
|
||||||
def fetch(self, url, body=None, headers=None):
|
|
||||||
stop = int(time.time()) + self.ALLOWED_TIME
|
|
||||||
off = self.ALLOWED_TIME
|
|
||||||
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
|
|
||||||
headers.setdefault('User-Agent',
|
|
||||||
"%s %s" % (USER_AGENT, pycurl.version,))
|
|
||||||
|
|
||||||
header_list = []
|
|
||||||
if headers is not None:
|
|
||||||
for header_name, header_value in headers.iteritems():
|
|
||||||
header_list.append('%s: %s' % (header_name, header_value))
|
|
||||||
|
|
||||||
c = pycurl.Curl()
|
|
||||||
try:
|
|
||||||
c.setopt(pycurl.NOSIGNAL, 1)
|
|
||||||
|
|
||||||
if header_list:
|
|
||||||
c.setopt(pycurl.HTTPHEADER, header_list)
|
|
||||||
|
|
||||||
# Presence of a body indicates that we should do a POST
|
|
||||||
if body is not None:
|
|
||||||
c.setopt(pycurl.POST, 1)
|
|
||||||
c.setopt(pycurl.POSTFIELDS, body)
|
|
||||||
|
|
||||||
while off > 0:
|
|
||||||
if not self._checkURL(url):
|
|
||||||
raise HTTPError("Fetching URL not allowed: %r" % (url,))
|
|
||||||
|
|
||||||
data = cStringIO.StringIO()
|
|
||||||
def write_data(chunk):
|
|
||||||
if data.tell() > 1024*MAX_RESPONSE_KB:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return data.write(chunk)
|
|
||||||
|
|
||||||
response_header_data = cStringIO.StringIO()
|
|
||||||
c.setopt(pycurl.WRITEFUNCTION, write_data)
|
|
||||||
c.setopt(pycurl.HEADERFUNCTION, response_header_data.write)
|
|
||||||
c.setopt(pycurl.TIMEOUT, off)
|
|
||||||
c.setopt(pycurl.URL, openid.urinorm.urinorm(url))
|
|
||||||
|
|
||||||
c.perform()
|
|
||||||
|
|
||||||
response_headers = self._parseHeaders(response_header_data)
|
|
||||||
code = c.getinfo(pycurl.RESPONSE_CODE)
|
|
||||||
if code in [301, 302, 303, 307]:
|
|
||||||
url = response_headers.get('location')
|
|
||||||
if url is None:
|
|
||||||
raise HTTPError(
|
|
||||||
'Redirect (%s) returned without a location' % code)
|
|
||||||
|
|
||||||
# Redirects are always GETs
|
|
||||||
c.setopt(pycurl.POST, 0)
|
|
||||||
|
|
||||||
# There is no way to reset POSTFIELDS to empty and
|
|
||||||
# reuse the connection, but we only use it once.
|
|
||||||
else:
|
|
||||||
resp = HTTPResponse()
|
|
||||||
resp.headers = response_headers
|
|
||||||
resp.status = code
|
|
||||||
resp.final_url = url
|
|
||||||
resp.body = data.getvalue()
|
|
||||||
return resp
|
|
||||||
|
|
||||||
off = stop - int(time.time())
|
|
||||||
|
|
||||||
raise HTTPError("Timed out fetching: %r" % (url,))
|
|
||||||
finally:
|
|
||||||
c.close()
|
|
||||||
|
|
||||||
class HTTPLib2Fetcher(HTTPFetcher):
|
|
||||||
"""A fetcher that uses C{httplib2} for performing HTTP
|
|
||||||
requests. This implementation supports HTTP caching.
|
|
||||||
|
|
||||||
@see: http://bitworking.org/projects/httplib2/
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, cache=None):
|
|
||||||
"""@param cache: An object suitable for use as an C{httplib2}
|
|
||||||
cache. If a string is passed, it is assumed to be a
|
|
||||||
directory name.
|
|
||||||
"""
|
|
||||||
if httplib2 is None:
|
|
||||||
raise RuntimeError('Cannot find httplib2 library. '
|
|
||||||
'See http://bitworking.org/projects/httplib2/')
|
|
||||||
|
|
||||||
super(HTTPLib2Fetcher, self).__init__()
|
|
||||||
|
|
||||||
# An instance of the httplib2 object that performs HTTP requests
|
|
||||||
self.httplib2 = httplib2.Http(cache)
|
|
||||||
|
|
||||||
# We want httplib2 to raise exceptions for errors, just like
|
|
||||||
# the other fetchers.
|
|
||||||
self.httplib2.force_exception_to_status_code = False
|
|
||||||
|
|
||||||
def fetch(self, url, body=None, headers=None):
|
|
||||||
"""Perform an HTTP request
|
|
||||||
|
|
||||||
@raises Exception: Any exception that can be raised by httplib2
|
|
||||||
|
|
||||||
@see: C{L{HTTPFetcher.fetch}}
|
|
||||||
"""
|
|
||||||
if body:
|
|
||||||
method = 'POST'
|
|
||||||
else:
|
|
||||||
method = 'GET'
|
|
||||||
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
|
|
||||||
# httplib2 doesn't check to make sure that the URL's scheme is
|
|
||||||
# 'http' so we do it here.
|
|
||||||
if not (url.startswith('http://') or url.startswith('https://')):
|
|
||||||
raise ValueError('URL is not a HTTP URL: %r' % (url,))
|
|
||||||
|
|
||||||
httplib2_response, content = self.httplib2.request(
|
|
||||||
url, method, body=body, headers=headers)
|
|
||||||
|
|
||||||
# Translate the httplib2 response to our HTTP response abstraction
|
|
||||||
|
|
||||||
# When a 400 is returned, there is no "content-location"
|
|
||||||
# header set. This seems like a bug to me. I can't think of a
|
|
||||||
# case where we really care about the final URL when it is an
|
|
||||||
# error response, but being careful about it can't hurt.
|
|
||||||
try:
|
|
||||||
final_url = httplib2_response['content-location']
|
|
||||||
except KeyError:
|
|
||||||
# We're assuming that no redirects occurred
|
|
||||||
assert not httplib2_response.previous
|
|
||||||
|
|
||||||
# And this should never happen for a successful response
|
|
||||||
assert httplib2_response.status != 200
|
|
||||||
final_url = url
|
|
||||||
|
|
||||||
return HTTPResponse(
|
|
||||||
body=content,
|
|
||||||
final_url=final_url,
|
|
||||||
headers=dict(httplib2_response.items()),
|
|
||||||
status=httplib2_response.status,
|
|
||||||
)
|
|
Loading…
Add table
Add a link
Reference in a new issue