xrires.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # -*- test-case-name: openid.test.test_xrires -*-
  2. """XRI resolution.
  3. """
  4. from urllib import urlencode
  5. from openid import fetchers
  6. from openid.yadis import etxrd
  7. from openid.yadis.xri import toURINormal
  8. from openid.yadis.services import iterServices
  9. DEFAULT_PROXY = 'http://proxy.xri.net/'
  10. class ProxyResolver(object):
  11. """Python interface to a remote XRI proxy resolver.
  12. """
  13. def __init__(self, proxy_url=DEFAULT_PROXY):
  14. self.proxy_url = proxy_url
  15. def queryURL(self, xri, service_type=None):
  16. """Build a URL to query the proxy resolver.
  17. @param xri: An XRI to resolve.
  18. @type xri: unicode
  19. @param service_type: The service type to resolve, if you desire
  20. service endpoint selection. A service type is a URI.
  21. @type service_type: str
  22. @returns: a URL
  23. @returntype: str
  24. """
  25. # Trim off the xri:// prefix. The proxy resolver didn't accept it
  26. # when this code was written, but that may (or may not) change for
  27. # XRI Resolution 2.0 Working Draft 11.
  28. qxri = toURINormal(xri)[6:]
  29. hxri = self.proxy_url + qxri
  30. args = {
  31. # XXX: If the proxy resolver will ensure that it doesn't return
  32. # bogus CanonicalIDs (as per Steve's message of 15 Aug 2006
  33. # 11:13:42), then we could ask for application/xrd+xml instead,
  34. # which would give us a bit less to process.
  35. '_xrd_r': 'application/xrds+xml',
  36. }
  37. if service_type:
  38. args['_xrd_t'] = service_type
  39. else:
  40. # Don't perform service endpoint selection.
  41. args['_xrd_r'] += ';sep=false'
  42. query = _appendArgs(hxri, args)
  43. return query
  44. def query(self, xri, service_types):
  45. """Resolve some services for an XRI.
  46. Note: I don't implement any service endpoint selection beyond what
  47. the resolver I'm querying does, so the Services I return may well
  48. include Services that were not of the types you asked for.
  49. May raise fetchers.HTTPFetchingError or L{etxrd.XRDSError} if
  50. the fetching or parsing don't go so well.
  51. @param xri: An XRI to resolve.
  52. @type xri: unicode
  53. @param service_types: A list of services types to query for. Service
  54. types are URIs.
  55. @type service_types: list of str
  56. @returns: tuple of (CanonicalID, Service elements)
  57. @returntype: (unicode, list of C{ElementTree.Element}s)
  58. """
  59. # FIXME: No test coverage!
  60. services = []
  61. # Make a seperate request to the proxy resolver for each service
  62. # type, as, if it is following Refs, it could return a different
  63. # XRDS for each.
  64. canonicalID = None
  65. for service_type in service_types:
  66. url = self.queryURL(xri, service_type)
  67. response = fetchers.fetch(url)
  68. if response.status not in (200, 206):
  69. # XXX: sucks to fail silently.
  70. # print "response not OK:", response
  71. continue
  72. et = etxrd.parseXRDS(response.body)
  73. canonicalID = etxrd.getCanonicalID(xri, et)
  74. some_services = list(iterServices(et))
  75. services.extend(some_services)
  76. # TODO:
  77. # * If we do get hits for multiple service_types, we're almost
  78. # certainly going to have duplicated service entries and
  79. # broken priority ordering.
  80. return canonicalID, services
  81. def _appendArgs(url, args):
  82. """Append some arguments to an HTTP query.
  83. """
  84. # to be merged with oidutil.appendArgs when we combine the projects.
  85. if hasattr(args, 'items'):
  86. args = args.items()
  87. args.sort()
  88. if len(args) == 0:
  89. return url
  90. # According to XRI Resolution section "QXRI query parameters":
  91. #
  92. # """If the original QXRI had a null query component (only a leading
  93. # question mark), or a query component consisting of only question
  94. # marks, one additional leading question mark MUST be added when
  95. # adding any XRI resolution parameters."""
  96. if '?' in url.rstrip('?'):
  97. sep = '&'
  98. else:
  99. sep = '?'
  100. return '%s%s%s' % (url, sep, urlencode(args))