Remove python-rd requirement and make compatible with RFC 7033

pull/9/head
Jeremy Carbaugh 2013-11-01 12:26:47 -04:00
rodzic 2a7fa54571
commit c5936e4e5d
2 zmienionych plików z 63 dodań i 93 usunięć

Wyświetl plik

@ -1,2 +1 @@
rd==0.1 requests==2.0.1
requests==0.14.2

Wyświetl plik

@ -1,10 +1,7 @@
import logging import logging
import urllib
import requests import requests
import rd __version__ = '1.0'
__version__ = '0.3'
RELS = { RELS = {
'activity_streams': 'http://activitystrea.ms/spec/1.0', 'activity_streams': 'http://activitystrea.ms/spec/1.0',
@ -17,12 +14,7 @@ RELS = {
'xfn': 'http://gmpg.org/xfn/11', 'xfn': 'http://gmpg.org/xfn/11',
} }
WEBFINGER_TYPES = ( WEBFINGER_TYPE = 'application/jrd+json'
'lrdd', # current
'http://lrdd.net/rel/descriptor', # deprecated on 12/11/2009
'http://webfinger.net/rel/acct-desc', # deprecated on 11/26/2009
'http://webfinger.info/rel/service', # deprecated on 09/17/2009
)
UNOFFICIAL_ENDPOINTS = { UNOFFICIAL_ENDPOINTS = {
'facebook.com': 'facebook-webfinger.appspot.com', 'facebook.com': 'facebook-webfinger.appspot.com',
@ -37,120 +29,102 @@ class WebFingerException(Exception):
class WebFingerResponse(object): class WebFingerResponse(object):
""" Response that wraps an RD object. This class provides a `secure` """ Response that wraps an RD object. It provides attribute-style access
parameter to indicate whether the response was returned via HTTP or to links for specific rels, responding with the href attribute
HTTPS. It also provides attribute-style access to links for specific of the matched element.
rels, responding with the href attribute of the matched element.
""" """
def __init__(self, rd, secure=False): def __init__(self, jrd):
self.secure = secure self.jrd = jrd
self.rd = rd
def __getattr__(self, name): def __getattr__(self, name):
if name in RELS: if name in RELS:
return self.rd.find_link(RELS[name], attr='href') return self.rel(RELS[name])
return getattr(self.rd, name) return getattr(self.jrd, name)
@property
def subject(self):
return self.jrd.get('subject')
@property
def aliases(self):
return self.jrd.get('aliases')
@property
def properties(self):
return self.jrd.get('properties')
@property
def links(self):
return self.jrd.get('links')
def rel(self, relation, attr='href'):
links = self.links
if links:
for link in links:
if link.get('rel') == relation:
return link.get(attr)
class WebFingerClient(object): class WebFingerClient(object):
def __init__(self, timeout=None, official=False): def __init__(self, timeout=None, official=False):
self._official = official self.official = official
self._session = requests.session( self.timeout = timeout
timeout=timeout,
headers={'User-Agent': 'python-webfinger/%s' % __version__})
def rd(self, url, raw=False): def jrd(self, host, resource, rel, raw=False):
""" Load resource at given URL and attempt to parse either XRD or JRD """ Load resource at given URL and attempt to parse either XRD or JRD
based on HTTP response Content-Type header. based on HTTP response Content-Type header.
""" """
resp = self._session.get(url)
content = resp.content
return content if raw else rd.loads(content, resp.headers.get('Content-Type'))
def hostmeta(self, host, resource=None, rel=None, secure=True, raw=False): url = "https://%s/.well-known/webfinger" % host
""" Load host-meta resource from WebFinger provider.
Defaults to a secure (SSL) connection unless secure=False.
"""
protocol = "https" if secure else "http" headers = {
'User-Agent': 'python-webfinger/%s' % __version__,
'Accept': WEBFINGER_TYPE,
}
# create querystring params params = {'resource': resource}
params = {}
if resource:
params['resource'] = resource
if rel: if rel:
params['rel'] = rel params['rel'] = rel
# use unofficial endpoint, if enabled resp = requests.get(url, params=params, headers=headers, timeout=self.timeout, verify=True)
if not self._official and host in UNOFFICIAL_ENDPOINTS:
host = UNOFFICIAL_ENDPOINTS[host]
# create full host-meta URL logging.debug('fetching JRD from %s' % resp.url)
url = "%s://%s/.well-known/host-meta" % (protocol, host)
# attempt to load from host-meta.json resource
resp = self._session.get("%s.json" % url, params=params)
if resp.status_code == 404:
# on failure, load from RFC 6415 host-meta resource
resp = self._session.get(url, params=params, headers={"Accept": "application/json"})
if resp.status_code != 200:
# raise error if request was not successful
raise WebFingerException("host-meta not found")
# load XRD or JRD based on HTTP response Content-Type header
content = resp.content content = resp.content
return content if raw else rd.loads(content, resp.headers.get('Content-Type')) content_type = resp.headers.get('Content-Type')
def finger(self, subject, resource=None, rel=None): logging.debug('response content type: %s' % content_type)
if content_type != WEBFINGER_TYPE:
raise WebFingerException('Invalid response type from server')
if raw:
return content
return resp.json()
def finger(self, resource, rel=None):
""" Perform a WebFinger query based on the given subject. """ Perform a WebFinger query based on the given subject.
The `rel` parameter, if specified, will be passed to the provider, The `rel` parameter, if specified, will be passed to the provider,
but be aware that providers are not required to implement the but be aware that providers are not required to implement the
rel filter. rel filter.
""" """
host = resource.split("@")[-1]
host = subject.split("@")[-1] jrd = self.jrd(host, resource, rel)
return WebFingerResponse(jrd)
try:
# attempt SSL host-meta retrieval
hm = self.hostmeta(host, resource=resource, rel=rel)
secure = True
except (requests.RequestException, requests.HTTPError):
# on failure, attempt non-SSL
hm = self.hostmeta(host, resource=resource, rel=rel, secure=False)
secure = False
if hm is None:
raise WebFingerException("Unable to load or parse host-meta")
if resource and hm.subject == resource:
# resource query worked, return LRDD response
return WebFingerResponse(hm, secure)
else:
# find template for LRDD document
template = hm.find_link(WEBFINGER_TYPES, attr='template')
secure = template.startswith('https://')
url = template.replace('{uri}', urllib.quote_plus(subject))
lrdd = self.rd(url)
return WebFingerResponse(lrdd, secure)
def finger(subject, resource=None, rel=None, timeout=None, official=False): def finger(resource, rel=None, timeout=None, official=False):
""" Shortcut method for invoking WebFingerClient. """ Shortcut method for invoking WebFingerClient.
""" """
if ":" not in subject: if ":" not in resource:
raise WebFingerException("scheme is required in subject URI") raise WebFingerException("scheme is required in subject URI")
client = WebFingerClient(timeout=timeout, official=official) client = WebFingerClient(timeout=timeout, official=official)
return client.finger(subject, resource=resource, rel=rel) return client.finger(resource, rel=rel)
if __name__ == '__main__': if __name__ == '__main__':
@ -187,7 +161,4 @@ if __name__ == '__main__':
print "Open Social: ", wf.opensocial print "Open Social: ", wf.opensocial
print "Portable Contacts: ", wf.portable_contacts print "Portable Contacts: ", wf.portable_contacts
print "Profile: ", wf.profile print "Profile: ", wf.profile
print "XFN: ", wf.find_link("http://gmpg.org/xfn/11", attr="href") print "XFN: ", wf.rel("http://gmpg.org/xfn/11")
if not wf.secure:
print "\n*** Warning: Data was retrieved over an insecure connection"