discover.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # -*- test-case-name: openid.test.test_yadis_discover -*-
  2. __all__ = ['discover', 'DiscoveryResult', 'DiscoveryFailure']
  3. from cStringIO import StringIO
  4. from openid import fetchers
  5. from openid.yadis.constants import \
  6. YADIS_HEADER_NAME, YADIS_CONTENT_TYPE, YADIS_ACCEPT_HEADER
  7. from openid.yadis.parsehtml import MetaNotFound, findHTMLMeta
  8. class DiscoveryFailure(Exception):
  9. """Raised when a YADIS protocol error occurs in the discovery process"""
  10. identity_url = None
  11. def __init__(self, message, http_response):
  12. Exception.__init__(self, message)
  13. self.http_response = http_response
  14. class DiscoveryResult(object):
  15. """Contains the result of performing Yadis discovery on a URI"""
  16. # The URI that was passed to the fetcher
  17. request_uri = None
  18. # The result of following redirects from the request_uri
  19. normalized_uri = None
  20. # The URI from which the response text was returned (set to
  21. # None if there was no XRDS document found)
  22. xrds_uri = None
  23. # The content-type returned with the response_text
  24. content_type = None
  25. # The document returned from the xrds_uri
  26. response_text = None
  27. def __init__(self, request_uri):
  28. """Initialize the state of the object
  29. sets all attributes to None except the request_uri
  30. """
  31. self.request_uri = request_uri
  32. def usedYadisLocation(self):
  33. """Was the Yadis protocol's indirection used?"""
  34. return self.normalized_uri != self.xrds_uri
  35. def isXRDS(self):
  36. """Is the response text supposed to be an XRDS document?"""
  37. return (self.usedYadisLocation() or
  38. self.content_type == YADIS_CONTENT_TYPE)
  39. def discover(uri):
  40. """Discover services for a given URI.
  41. @param uri: The identity URI as a well-formed http or https
  42. URI. The well-formedness and the protocol are not checked, but
  43. the results of this function are undefined if those properties
  44. do not hold.
  45. @return: DiscoveryResult object
  46. @raises Exception: Any exception that can be raised by fetching a URL with
  47. the given fetcher.
  48. @raises DiscoveryFailure: When the HTTP response does not have a 200 code.
  49. """
  50. result = DiscoveryResult(uri)
  51. resp = fetchers.fetch(uri, headers={'Accept': YADIS_ACCEPT_HEADER})
  52. if resp.status not in (200, 206):
  53. raise DiscoveryFailure(
  54. 'HTTP Response status from identity URL host is not 200. '
  55. 'Got status %r' % (resp.status,), resp)
  56. # Note the URL after following redirects
  57. result.normalized_uri = resp.final_url
  58. # Attempt to find out where to go to discover the document
  59. # or if we already have it
  60. result.content_type = resp.headers.get('content-type')
  61. result.xrds_uri = whereIsYadis(resp)
  62. if result.xrds_uri and result.usedYadisLocation():
  63. resp = fetchers.fetch(result.xrds_uri)
  64. if resp.status not in (200, 206):
  65. exc = DiscoveryFailure(
  66. 'HTTP Response status from Yadis host is not 200. '
  67. 'Got status %r' % (resp.status,), resp)
  68. exc.identity_url = result.normalized_uri
  69. raise exc
  70. result.content_type = resp.headers.get('content-type')
  71. result.response_text = resp.body
  72. return result
  73. def whereIsYadis(resp):
  74. """Given a HTTPResponse, return the location of the Yadis document.
  75. May be the URL just retrieved, another URL, or None, if I can't
  76. find any.
  77. [non-blocking]
  78. @returns: str or None
  79. """
  80. # Attempt to find out where to go to discover the document
  81. # or if we already have it
  82. content_type = resp.headers.get('content-type')
  83. # According to the spec, the content-type header must be an exact
  84. # match, or else we have to look for an indirection.
  85. if (content_type and
  86. content_type.split(';', 1)[0].lower() == YADIS_CONTENT_TYPE):
  87. return resp.final_url
  88. else:
  89. # Try the header
  90. yadis_loc = resp.headers.get(YADIS_HEADER_NAME.lower())
  91. if not yadis_loc:
  92. # Parse as HTML if the header is missing.
  93. #
  94. # XXX: do we want to do something with content-type, like
  95. # have a whitelist or a blacklist (for detecting that it's
  96. # HTML)?
  97. try:
  98. yadis_loc = findHTMLMeta(StringIO(resp.body))
  99. except MetaNotFound:
  100. pass
  101. return yadis_loc