1
# -*- test-case-name: openid.test.test_yadis_discover -*-
2
__all__ = ['discover', 'DiscoveryResult', 'DiscoveryFailure']
4
from cStringIO import StringIO
6
from openid import fetchers
8
from openid.yadis.constants import \
9
YADIS_HEADER_NAME, YADIS_CONTENT_TYPE, YADIS_ACCEPT_HEADER
10
from openid.yadis.parsehtml import MetaNotFound, findHTMLMeta
12
class DiscoveryFailure(Exception):
13
"""Raised when a YADIS protocol error occurs in the discovery process"""
16
def __init__(self, message, http_response):
17
Exception.__init__(self, message)
18
self.http_response = http_response
20
class DiscoveryResult(object):
21
"""Contains the result of performing Yadis discovery on a URI"""
23
# The URI that was passed to the fetcher
26
# The result of following redirects from the request_uri
29
# The URI from which the response text was returned (set to
30
# None if there was no XRDS document found)
33
# The content-type returned with the response_text
36
# The document returned from the xrds_uri
39
def __init__(self, request_uri):
40
"""Initialize the state of the object
42
sets all attributes to None except the request_uri
44
self.request_uri = request_uri
46
def usedYadisLocation(self):
47
"""Was the Yadis protocol's indirection used?"""
48
return self.normalized_uri != self.xrds_uri
51
"""Is the response text supposed to be an XRDS document?"""
52
return (self.usedYadisLocation() or
53
self.content_type == YADIS_CONTENT_TYPE)
56
"""Discover services for a given URI.
58
@param uri: The identity URI as a well-formed http or https
59
URI. The well-formedness and the protocol are not checked, but
60
the results of this function are undefined if those properties
63
@return: DiscoveryResult object
65
@raises Exception: Any exception that can be raised by fetching a URL with
68
result = DiscoveryResult(uri)
69
resp = fetchers.fetch(uri, headers={'Accept': YADIS_ACCEPT_HEADER})
70
if resp.status != 200:
71
raise DiscoveryFailure(
72
'HTTP Response status from identity URL host is not 200. '
73
'Got status %r' % (resp.status,), resp)
75
# Note the URL after following redirects
76
result.normalized_uri = resp.final_url
78
# Attempt to find out where to go to discover the document
79
# or if we already have it
80
result.content_type = resp.headers.get('content-type')
82
result.xrds_uri = whereIsYadis(resp)
84
if result.xrds_uri and result.usedYadisLocation():
85
resp = fetchers.fetch(result.xrds_uri)
86
if resp.status != 200:
87
exc = DiscoveryFailure(
88
'HTTP Response status from Yadis host is not 200. '
89
'Got status %r' % (resp.status,), resp)
90
exc.identity_url = result.normalized_uri
92
result.content_type = resp.headers.get('content-type')
94
result.response_text = resp.body
99
def whereIsYadis(resp):
100
"""Given a HTTPResponse, return the location of the Yadis document.
102
May be the URL just retrieved, another URL, or None, if I can't
107
@returns: str or None
109
# Attempt to find out where to go to discover the document
110
# or if we already have it
111
content_type = resp.headers.get('content-type')
113
# According to the spec, the content-type header must be an exact
114
# match, or else we have to look for an indirection.
116
content_type.split(';', 1)[0].lower() == YADIS_CONTENT_TYPE):
117
return resp.final_url
120
yadis_loc = resp.headers.get(YADIS_HEADER_NAME.lower())
123
# Parse as HTML if the header is missing.
125
# XXX: do we want to do something with content-type, like
126
# have a whitelist or a blacklist (for detecting that it's
129
yadis_loc = findHTMLMeta(StringIO(resp.body))