incubator-heraldry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ket...@apache.org
Subject svn commit: r463060 [6/6] - in /incubator/heraldry/libraries/python/openid/trunk: ./ admin/ examples/ openid/ openid/consumer/ openid/server/ openid/store/ openid/test/ openid/test/data/ openid/yadis/
Date Wed, 11 Oct 2006 23:22:36 GMT
Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/discover.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/discover.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/discover.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/discover.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,117 @@
+# -*- test-case-name: yadis.test.test_discover -*-
+__all__ = ['discover', 'DiscoveryResult', 'DiscoveryFailure']
+
+from cStringIO import StringIO
+
+from openid import fetchers
+
+from openid.yadis.constants import \
+     YADIS_HEADER_NAME, YADIS_CONTENT_TYPE, YADIS_ACCEPT_HEADER
+from openid.yadis.parsehtml import MetaNotFound, findHTMLMeta
+
+class DiscoveryFailure(Exception):
+    """Raised when a YADIS protocol error occurs in the discovery process"""
+    identity_url = None
+
+    def __init__(self, message, http_response):
+        Exception.__init__(self, message)
+        self.http_response = http_response
+
+class DiscoveryResult(object):
+    """Contains the result of performing Yadis discovery on a URI"""
+
+    # The URI that was passed to the fetcher
+    request_uri = None
+
+    # The result of following redirects from the request_uri
+    normalized_uri = None
+
+    # The URI from which the response text was returned (set to
+    # None if there was no XRDS document found)
+    xrds_uri = None
+
+    # The content-type returned with the response_text
+    content_type = None
+
+    # The document returned from the xrds_uri
+    response_text = None
+
+    def __init__(self, request_uri):
+        """Initialize the state of the object
+
+        sets all attributes to None except the request_uri
+        """
+        self.request_uri = request_uri
+
+    def usedYadisLocation(self):
+        """Was the Yadis protocol's indirection used?"""
+        return self.normalized_uri == self.xrds_uri
+
+    def isXRDS(self):
+        """Is the response text supposed to be an XRDS document?"""
+        return (self.usedYadisLocation() or
+                self.content_type == YADIS_CONTENT_TYPE)
+
+def discover(uri):
+    """Discover services for a given URI.
+
+    @param uri: The identity URI as a well-formed http or https
+        URI. The well-formedness and the protocol are not checked, but
+        the results of this function are undefined if those properties
+        do not hold.
+
+    @return: DiscoveryResult object
+
+    @raises Exception: Any exception that can be raised by fetching a URL with
+        the given fetcher.
+    """
+    result = DiscoveryResult(uri)
+    resp = fetchers.fetch(uri, headers={'Accept': YADIS_ACCEPT_HEADER})
+    if resp.status != 200:
+        raise DiscoveryFailure(
+            'HTTP Response status from identity URL host is not 200. '
+            'Got status %r' % (resp.status,), resp)
+
+    # Note the URL after following redirects
+    result.normalized_uri = resp.final_url
+
+    # Attempt to find out where to go to discover the document
+    # or if we already have it
+    result.content_type = resp.headers.get('content-type')
+
+    # According to the spec, the content-type header must be an exact
+    # match, or else we have to look for an indirection.
+    if (result.content_type and
+        result.content_type.split(';', 1)[0].lower() == YADIS_CONTENT_TYPE):
+        result.xrds_uri = result.normalized_uri
+    else:
+        # Try the header
+        yadis_loc = resp.headers.get(YADIS_HEADER_NAME.lower())
+
+        if not yadis_loc:
+            # Parse as HTML if the header is missing.
+            #
+            # XXX: do we want to do something with content-type, like
+            # have a whitelist or a blacklist (for detecting that it's
+            # HTML)?
+            try:
+                yadis_loc = findHTMLMeta(StringIO(resp.body))
+            except MetaNotFound:
+                pass
+
+        # At this point, we have not found a YADIS Location URL. We
+        # will return the content that we scanned so that the caller
+        # can try to treat it as an XRDS if it wishes.
+        if yadis_loc:
+            result.xrds_uri = yadis_loc
+            resp = fetchers.fetch(yadis_loc)
+            if resp.status != 200:
+                exc = DiscoveryFailure(
+                    'HTTP Response status from Yadis host is not 200. '
+                    'Got status %r' % (resp.status,), resp)
+                exc.identity_url = result.normalized_uri
+                raise exc
+            result.content_type = resp.headers.get('content-type')
+
+    result.response_text = resp.body
+    return result

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/etxrd.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/etxrd.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/etxrd.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/etxrd.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,269 @@
+# -*- test-case-name: yadis.test.test_etxrd -*-
+"""
+ElementTree interface to an XRD document.
+"""
+
+__all__ = [
+    'nsTag',
+    'mkXRDTag',
+    'isXRDS',
+    'parseXRDS',
+    'getCanonicalID',
+    'getYadisXRD',
+    'getPriorityStrict',
+    'getPriority',
+    'prioSort',
+    'iterServices',
+    'expandService',
+    'expandServices',
+    ]
+
+import random
+
+from elementtree.ElementTree import ElementTree
+
+# Use expat if it's present. Otherwise, use xmllib
+try:
+    from xml.parsers.expat import ExpatError as XMLError
+    from elementtree.ElementTree import XMLTreeBuilder
+except ImportError:
+    from elementtree.SimpleXMLTreeBuilder import TreeBuilder as XMLTreeBuilder
+    from xmllib import Error as XMLError
+
+from openid.yadis import xri
+
+class XRDSError(Exception):
+    """An error with the XRDS document."""
+
+    # The exception that triggered this exception
+    reason = None
+
+
+
+class XRDSFraud(XRDSError):
+    """Raised when there's an assertion in the XRDS that it does not have
+    the authority to make.
+    """
+
+
+
+def parseXRDS(text):
+    """Parse the given text as an XRDS document.
+
+    @return: ElementTree containing an XRDS document
+
+    @raises XRDSError: When there is a parse error or the document does
+        not contain an XRDS.
+    """
+    try:
+        parser = XMLTreeBuilder()
+        parser.feed(text)
+        element = parser.close()
+    except XMLError, why:
+        exc = XRDSError('Error parsing document as XML')
+        exc.reason = why
+        raise exc
+    else:
+        tree = ElementTree(element)
+        if not isXRDS(tree):
+            raise XRDSError('Not an XRDS document')
+
+        return tree
+
+XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'
+XRDS_NS = 'xri://$xrds'
+
+def nsTag(ns, t):
+    return '{%s}%s' % (ns, t)
+
+def mkXRDTag(t):
+    """basestring -> basestring
+
+    Create a tag name in the XRD 2.0 XML namespace suitable for using
+    with ElementTree
+    """
+    return nsTag(XRD_NS_2_0, t)
+
+def mkXRDSTag(t):
+    """basestring -> basestring
+
+    Create a tag name in the XRDS XML namespace suitable for using
+    with ElementTree
+    """
+    return nsTag(XRDS_NS, t)
+
+# Tags that are used in Yadis documents
+root_tag = mkXRDSTag('XRDS')
+service_tag = mkXRDTag('Service')
+xrd_tag = mkXRDTag('XRD')
+type_tag = mkXRDTag('Type')
+uri_tag = mkXRDTag('URI')
+
+# Other XRD tags
+canonicalID_tag = mkXRDTag('CanonicalID')
+
+def isXRDS(xrd_tree):
+    """Is this document an XRDS document?"""
+    root = xrd_tree.getroot()
+    return root.tag == root_tag
+
+def getYadisXRD(xrd_tree):
+    """Return the XRD element that should contain the Yadis services"""
+    xrd = None
+
+    # for the side-effect of assigning the last one in the list to the
+    # xrd variable
+    for xrd in xrd_tree.findall(xrd_tag):
+        pass
+
+    # There were no elements found, or else xrd would be set to the
+    # last one
+    if xrd is None:
+        raise XRDSError('No XRD present in tree')
+
+    return xrd
+
+
+def getCanonicalID(iname, xrd_tree):
+    """Return the CanonicalID from this XRDS document.
+
+    @param iname: the XRI being resolved.
+    @type iname: unicode
+
+    @param xrd_tree: The XRDS output from the resolver.
+    @type xrd_tree: ElementTree
+
+    @returns: The XRI CanonicalID or None.
+    @returntype: unicode or None
+    """
+    xrd_list = xrd_tree.findall(xrd_tag)
+    xrd_list.reverse()
+
+    try:
+        canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[-1].text)
+    except IndexError:
+        return None
+
+    childID = canonicalID
+
+    for xrd in xrd_list[1:]:
+        # XXX: can't use rsplit until we require python >= 2.4.
+        parent_sought = childID[:childID.rindex('!')]
+        parent_list = [xri.XRI(c.text) for c in xrd.findall(canonicalID_tag)]
+        if parent_sought not in parent_list:
+            raise XRDSFraud("%r can not come from any of %s" % (parent_sought,
+                                                                parent_list))
+
+        childID = parent_sought
+
+    root = xri.rootAuthority(iname)
+    if not xri.providerIsAuthoritative(root, childID):
+        raise XRDSFraud("%r can not come from root %r" % (childID, root))
+
+    return canonicalID
+
+
+
+class _Max(object):
+    """Value that compares greater than any other value.
+
+    Should only be used as a singleton. Implemented for use as a
+    priority value for when a priority is not specified."""
+    def __cmp__(self, other):
+        if other is self:
+            return 0
+
+        return 1
+
+Max = _Max()
+
+def getPriorityStrict(element):
+    """Get the priority of this element.
+
+    Raises ValueError if the value of the priority is invalid. If no
+    priority is specified, it returns a value that compares greater
+    than any other value.
+    """
+    prio_str = element.get('priority')
+    if prio_str is not None:
+        prio_val = int(prio_str)
+        if prio_val >= 0:
+            return prio_val
+        else:
+            raise ValueError('Priority values must be non-negative integers')
+
+    # Any errors in parsing the priority fall through to here
+    return Max
+
+def getPriority(element):
+    """Get the priority of this element
+
+    Returns Max if no priority is specified or the priority value is invalid.
+    """
+    try:
+        return getPriorityStrict(element)
+    except ValueError:
+        return Max
+
+def prioSort(elements):
+    """Sort a list of elements that have priority attributes"""
+    # Randomize the services before sorting so that equal priority
+    # elements are load-balanced.
+    random.shuffle(elements)
+
+    prio_elems = [(getPriority(e), e) for e in elements]
+    prio_elems.sort()
+    sorted_elems = [s for (_, s) in prio_elems]
+    return sorted_elems
+
+def iterServices(xrd_tree):
+    """Return an iterable over the Service elements in the Yadis XRD
+
+    sorted by priority"""
+    xrd = getYadisXRD(xrd_tree)
+    return prioSort(xrd.findall(service_tag))
+
+def sortedURIs(service_element):
+    """Given a Service element, return a list of the contents of all
+    URI tags in priority order."""
+    return [uri_element.text for uri_element
+            in prioSort(service_element.findall(uri_tag))]
+
+def getTypeURIs(service_element):
+    """Given a Service element, return a list of the contents of all
+    Type tags"""
+    return [type_element.text for type_element
+            in service_element.findall(type_tag)]
+
+def expandService(service_element):
+    """Take a service element and expand it into an iterator of:
+    ([type_uri], uri, service_element)
+    """
+    uris = sortedURIs(service_element)
+    if not uris:
+        uris = [None]
+
+    expanded = []
+    for uri in uris:
+        type_uris = getTypeURIs(service_element)
+        expanded.append((type_uris, uri, service_element))
+
+    return expanded
+
+def expandServices(service_elements):
+    """Take a sorted iterator of service elements and expand it into a
+    sorted iterator of:
+    ([type_uri], uri, service_element)
+
+    There may be more than one item in the resulting list for each
+    service element if there is more than one URI or type for a
+    service, but each triple will be unique.
+
+    If there is no URI or Type for a Service element, it will not
+    appear in the result.
+    """
+    expanded = []
+    for service_element in service_elements:
+        expanded.extend(expandService(service_element))
+
+    return expanded

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/filters.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/filters.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/filters.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/filters.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,200 @@
+"""This module contains functions and classes used for extracting
+endpoint information out of a Yadis XRD file using the ElementTree XML
+parser.
+"""
+
+__all__ = [
+    'BasicServiceEndpoint',
+    'mkFilter',
+    'IFilter',
+    'TransformFilterMaker',
+    'CompoundFilter',
+    ]
+
+from openid.yadis.etxrd import expandService
+
+class BasicServiceEndpoint(object):
+    """Generic endpoint object that contains parsed service
+    information, as well as a reference to the service element from
+    which it was generated. If there is more than one xrd:Type or
+    xrd:URI in the xrd:Service, this object represents just one of
+    those pairs.
+
+    This object can be used as a filter, because it implements
+    fromBasicServiceEndpoint.
+
+    The simplest kind of filter you can write implements
+    fromBasicServiceEndpoint, which takes one of these objects.
+    """
+    def __init__(self, yadis_url, type_uris, uri, service_element):
+        self.type_uris = type_uris
+        self.yadis_url = yadis_url
+        self.uri = uri
+        self.service_element = service_element
+
+    def matchTypes(self, type_uris):
+        """Query this endpoint to see if it has any of the given type
+        URIs. This is useful for implementing other endpoint classes
+        that e.g. need to check for the presence of multiple versions
+        of a single protocol.
+
+        @param type_uris: The URIs that you wish to check
+        @type type_uris: iterable of str
+
+        @return: all types that are in both in type_uris and
+            self.type_uris
+        """
+        return [uri for uri in type_uris if uri in self.type_uris]
+
+    def fromBasicServiceEndpoint(endpoint):
+        """Trivial transform from a basic endpoint to itself. This
+        method exists to allow BasicServiceEndpoint to be used as a
+        filter.
+
+        If you are subclassing this object, re-implement this function.
+
+        @param endpoint: An instance of BasicServiceEndpoint
+        @return: The object that was passed in, with no processing.
+        """
+        return endpoint
+
+    fromBasicServiceEndpoint = staticmethod(fromBasicServiceEndpoint)
+
+class IFilter(object):
+    """Interface for Yadis filter objects. Other filter-like things
+    are convertable to this class."""
+
+    def getServiceEndpoints(self, yadis_url, service_element):
+        """Returns an iterator of endpoint objects"""
+        raise NotImplementedError
+
+class TransformFilterMaker(object):
+    """Take a list of basic filters and makes a filter that transforms
+    the basic filter into a top-level filter. This is mostly useful
+    for the implementation of mkFilter, which should only be needed
+    for special cases or internal use by this library.
+
+    This object is useful for creating simple filters for services
+    that use one URI and are specified by one Type (we expect most
+    Types will fit this paradigm).
+
+    Creates a BasicServiceEndpoint object and apply the filter
+    functions to it until one of them returns a value.
+    """
+
+    def __init__(self, filter_functions):
+        """Initialize the filter maker's state
+
+        @param filter_functions: The endpoint transformer functions to
+            apply to the basic endpoint. These are called in turn
+            until one of them does not return None, and the result of
+            that transformer is returned.
+        """
+        self.filter_functions = filter_functions
+
+    def getServiceEndpoints(self, yadis_url, service_element):
+        """Returns an iterator of endpoint objects produced by the
+        filter functions."""
+        endpoints = []
+
+        # Do an expansion of the service element by xrd:Type and xrd:URI
+        for type_uris, uri, _ in expandService(service_element):
+
+            # Create a basic endpoint object to represent this
+            # yadis_url, Service, Type, URI combination
+            endpoint = BasicServiceEndpoint(
+                yadis_url, type_uris, uri, service_element)
+
+            e = self.applyFilters(endpoint)
+            if e is not None:
+                endpoints.append(e)
+
+        return endpoints
+
+    def applyFilters(self, endpoint):
+        """Apply filter functions to an endpoint until one of them
+        returns non-None."""
+        for filter_function in self.filter_functions:
+            e = filter_function(endpoint)
+            if e is not None:
+                # Once one of the filters has returned an
+                # endpoint, do not apply any more.
+                return e
+
+        return None
+
+class CompoundFilter(object):
+    """Create a new filter that applies a set of filters to an endpoint
+    and collects their results.
+    """
+    def __init__(self, subfilters):
+        self.subfilters = subfilters
+
+    def getServiceEndpoints(self, yadis_url, service_element):
+        """Generate all endpoint objects for all of the subfilters of
+        this filter and return their concatenation."""
+        endpoints = []
+        for subfilter in self.subfilters:
+            endpoints.extend(
+                subfilter.getServiceEndpoints(yadis_url, service_element))
+        return endpoints
+
+# Exception raised when something is not able to be turned into a filter
+filter_type_error = TypeError(
+    'Expected a filter, an endpoint, a callable or a list of any of these.')
+
+def mkFilter(parts):
+    """Convert a filter-convertable thing into a filter
+
+    @param parts: a filter, an endpoint, a callable, or a list of any of these.
+    """
+    # Convert the parts into a list, and pass to mkCompoundFilter
+    if parts is None:
+        parts = [BasicServiceEndpoint]
+
+    try:
+        parts = list(parts)
+    except TypeError:
+        return mkCompoundFilter([parts])
+    else:
+        return mkCompoundFilter(parts)
+
+def mkCompoundFilter(parts):
+    """Create a filter out of a list of filter-like things
+
+    Used by mkFilter
+
+    @param parts: list of filter, endpoint, callable or list of any of these
+    """
+    # Separate into a list of callables and a list of filter objects
+    transformers = []
+    filters = []
+    for subfilter in parts:
+        try:
+            subfilter = list(subfilter)
+        except TypeError:
+            # If it's not an iterable
+            if hasattr(subfilter, 'getServiceEndpoints'):
+                # It's a full filter
+                filters.append(subfilter)
+            elif hasattr(subfilter, 'fromBasicServiceEndpoint'):
+                # It's an endpoint object, so put its endpoint
+                # conversion attribute into the list of endpoint
+                # transformers
+                transformers.append(subfilter.fromBasicServiceEndpoint)
+            elif callable(subfilter):
+                # It's a simple callable, so add it to the list of
+                # endpoint transformers
+                transformers.append(subfilter)
+            else:
+                raise filter_type_error
+        else:
+            filters.append(mkCompoundFilter(subfilter))
+
+    if transformers:
+        filters.append(TransformFilterMaker(transformers))
+
+    if len(filters) == 1:
+        return filters[0]
+    else:
+        return CompoundFilter(filters)

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/manager.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/manager.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/manager.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/manager.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,185 @@
+class YadisServiceManager(object):
+    """Holds the state of a list of selected Yadis services, managing
+    storing it in a session and iterating over the services in order."""
+
+    def __init__(self, starting_url, yadis_url, services, session_key):
+        # The URL that was used to initiate the Yadis protocol
+        self.starting_url = starting_url
+
+        # The URL after following redirects (the identifier)
+        self.yadis_url = yadis_url
+
+        # List of service elements
+        self.services = list(services)
+
+        self.session_key = session_key
+
+        # Reference to the current service object
+        self._current = None
+
+    def __len__(self):
+        """How many untried services remain?"""
+        return len(self.services)
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        """Return the next service
+
+        self.current() will continue to return that service until the
+        next call to this method."""
+        try:
+            self._current = self.services.pop(0)
+        except IndexError:
+            raise StopIteration
+        else:
+            return self._current
+
+    def current(self):
+        """Return the current service.
+
+        Returns None if there are no services left.
+        """
+        return self._current
+
+    def forURL(self, url):
+        return url in [self.starting_url, self.yadis_url]
+
+    def started(self):
+        """Has the first service been returned?"""
+        return self._current is not None
+
+    def store(self, session):
+        """Store this object in the session, by its session key."""
+        session[self.session_key] = self
+
+class Discovery(object):
+    """State management for discovery.
+
+    High-level usage pattern is to call .getNextService(discover) in
+    order to find the next available service for this user for this
+    session. Once a request completes, call .finish() to clean up the
+    session state.
+
+    @ivar session: a dict-like object that stores state unique to the
+        requesting user-agent. This object must be able to store
+        serializable objects.
+
+    @ivar url: the URL that is used to make the discovery request
+
+    @ivar session_key_suffix: The suffix that will be used to identify
+        this object in the session object.
+    """
+
+    DEFAULT_SUFFIX = 'auth'
+    PREFIX = '_yadis_services_'
+
+    def __init__(self, session, url, session_key_suffix=None):
+        """Initialize a discovery object"""
+        self.session = session
+        self.url = url
+        if session_key_suffix is None:
+            session_key_suffix = self.DEFAULT_SUFFIX
+
+        self.session_key_suffix = session_key_suffix
+
+    def getNextService(self, discover):
+        """Return the next authentication service for the pair of
+        user_input and session.  This function handles fallback.
+
+
+        @param discover: a callable that takes a URL and returns a
+            list of services
+
+        @type discover: str -> [service]
+
+
+        @return: the next available service
+        """
+        manager = self.getManager()
+        if manager is not None and not manager:
+            self.destroyManager()
+
+        if not manager:
+            yadis_url, services = discover(self.url)
+            manager = self.createManager(services, yadis_url)
+
+        if manager:
+            service = manager.next()
+            manager.store(self.session)
+        else:
+            service = None
+
+        return service
+
+    def cleanup(self):
+        """Clean up Yadis-related services in the session and return
+        the most-recently-attempted service from the manager, if one
+        exists.
+
+        @return: current service endpoint object or None if there is
+            no current service
+        """
+        manager = self.getManager()
+        if manager is not None:
+            service = manager.current()
+            self.destroyManager()
+        else:
+            service = None
+
+        return service
+
+    ### Lower-level methods
+
+    def getSessionKey(self):
+        """Get the session key for this starting URL and suffix
+
+        @return: The session key
+        @rtype: str
+        """
+        return self.PREFIX + self.session_key_suffix
+
+    def getManager(self):
+        """Extract the YadisServiceManager for this object's URL and
+        suffix from the session.
+
+        @return: The current YadisServiceManager, if it's for this
+            URL, or else None
+        """
+        manager = self.session.get(self.getSessionKey())
+        if (manager is not None and manager.forURL(self.url)):
+            return manager
+        else:
+            return None
+
+    def createManager(self, services, yadis_url=None):
+        """Create a new YadisService Manager for this starting URL and
+        suffix, and store it in the session.
+
+        @raises KeyError: When I already have a manager.
+
+        @return: A new YadisServiceManager or None
+        """
+        key = self.getSessionKey()
+        if self.getManager():
+            raise KeyError('There is already a %r manager for %r' %
+                           (key, self.url))
+
+        if not services:
+            return None
+
+        manager = YadisServiceManager(self.url, yadis_url, services, key)
+        manager.store(self.session)
+        return manager
+
+    def destroyManager(self):
+        """Delete any YadisServiceManager with this starting URL and
+        suffix from the session.
+
+        If there is no service manager or the service manager is for a
+        different URL, it silently does nothing.
+        """
+        if self.getManager() is not None:
+            key = self.getSessionKey()
+            del self.session[key]

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/parsehtml.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/parsehtml.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/parsehtml.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/parsehtml.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,197 @@
+__all__ = ['findHTMLMeta', 'MetaNotFound']
+
+from HTMLParser import HTMLParser, HTMLParseError
+import htmlentitydefs
+import re
+
+from openid.yadis.constants import YADIS_HEADER_NAME
+
+# Size of the chunks to search at a time (also the amount that gets
+# read at a time)
+CHUNK_SIZE = 1024 * 16 # 16 KB
+
+class ParseDone(Exception):
+    """Exception to hold the URI that was located when the parse is
+    finished. If the parse finishes without finding the URI, set it to
+    None."""
+
+class MetaNotFound(Exception):
+    """Exception to hold the content of the page if we did not find
+    the appropriate <meta> tag"""
+
+re_flags = re.IGNORECASE | re.UNICODE | re.VERBOSE
+ent_pat = r'''
+&
+
+(?: \#x (?P<hex> [a-f0-9]+ )
+|   \# (?P<dec> \d+ )
+|   (?P<word> \w+ )
+)
+
+;'''
+
+ent_re = re.compile(ent_pat, re_flags)
+
+def substituteMO(mo):
+    if mo.lastgroup == 'hex':
+        codepoint = int(mo.group('hex'), 16)
+    elif mo.lastgroup == 'dec':
+        codepoint = int(mo.group('dec'))
+    else:
+        assert mo.lastgroup == 'word'
+        codepoint = htmlentitydefs.name2codepoint.get(mo.group('word'))
+
+    if codepoint is None:
+        return mo.group()
+    else:
+        return unichr(codepoint)
+
+def substituteEntities(s):
+    return ent_re.sub(substituteMO, s)
+
+class YadisHTMLParser(HTMLParser):
+    """Parser that finds a meta http-equiv tag in the head of a html
+    document.
+
+    When feeding in data, if the tag is matched or it will never be
+    found, the parser will raise ParseDone with the uri as the first
+    attribute.
+
+    Parsing state diagram
+    =====================
+
+    Any unlisted input does not affect the state::
+
+                1, 2, 5                       8
+               +--------------------------+  +-+
+               |                          |  | |
+            4  |    3       1, 2, 5, 7    v  | v
+        TOP -> HTML -> HEAD ----------> TERMINATED
+        | |            ^  |               ^  ^
+        | | 3          |  |               |  |
+        | +------------+  +-> FOUND ------+  |
+        |                  6         8       |
+        | 1, 2                               |
+        +------------------------------------+
+
+      1. any of </body>, </html>, </head> -> TERMINATE
+      2. <body> -> TERMINATE
+      3. <head> -> HEAD
+      4. <html> -> HTML
+      5. <html> -> TERMINATE
+      6. <meta http-equiv='X-XRDS-Location'> -> FOUND
+      7. <head> -> TERMINATE
+      8. Any input -> TERMINATE
+    """
+    TOP = 0
+    HTML = 1
+    HEAD = 2
+    FOUND = 3
+    TERMINATED = 4
+
+    def __init__(self):
+        HTMLParser.__init__(self)
+        self.phase = self.TOP
+
+    def _terminate(self):
+        self.phase = self.TERMINATED
+        raise ParseDone(None)
+
+    def handle_endtag(self, tag):
+        # If we ever see an end of head, body, or html, bail out right away.
+        # [1]
+        if tag in ['head', 'body', 'html']:
+            self._terminate()
+
+    def handle_starttag(self, tag, attrs):
+        # if we ever see a start body tag, bail out right away, since
+        # we want to prevent the meta tag from appearing in the body
+        # [2]
+        if tag=='body':
+            self._terminate()
+
+        if self.phase == self.TOP:
+            # At the top level, allow a html tag or a head tag to move
+            # to the head or html phase
+            if tag == 'head':
+                # [3]
+                self.phase = self.HEAD
+            elif tag == 'html':
+                # [4]
+                self.phase = self.HTML
+
+        elif self.phase == self.HTML:
+            # if we are in the html tag, allow a head tag to move to
+            # the HEAD phase. If we get another html tag, then bail
+            # out
+            if tag == 'head':
+                # [3]
+                self.phase = self.HEAD
+            elif tag == 'html':
+                # [5]
+                self._terminate()
+
+        elif self.phase == self.HEAD:
+            # If we are in the head phase, look for the appropriate
+            # meta tag. If we get a head or body tag, bail out.
+            if tag == 'meta':
+                attrs_d = dict(attrs)
+                http_equiv = attrs_d.get('http-equiv', '').lower()
+                if http_equiv == YADIS_HEADER_NAME.lower():
+                    raw_attr = attrs_d.get('content')
+                    yadis_loc = substituteEntities(raw_attr)
+                    # [6]
+                    self.phase = self.FOUND
+                    raise ParseDone(yadis_loc)
+
+            elif tag in ['head', 'html']:
+                # [5], [7]
+                self._terminate()
+
+    def feed(self, chars):
+        # [8]
+        if self.phase in [self.TERMINATED, self.FOUND]:
+            self._terminate()
+
+        return HTMLParser.feed(self, chars)
+
+def findHTMLMeta(stream):
+    """Look for a meta http-equiv tag with the YADIS header name.
+
+    @param stream: Source of the html text
+    @type stream: Object that implements a read() method that works
+        like file.read
+
+    @return: The URI from which to fetch the XRDS document
+    @rtype: str
+
+    @raises MetaNotFound: raised with the content that was
+        searched as the first parameter.
+    """
+    parser = YadisHTMLParser()
+    chunks = []
+
+    while 1:
+        chunk = stream.read(CHUNK_SIZE)
+        if not chunk:
+            # End of file
+            break
+
+        chunks.append(chunk)
+        try:
+            parser.feed(chunk)
+        except HTMLParseError, why:
+            # HTML parse error, so bail
+            chunks.append(stream.read())
+            break
+        except ParseDone, why:
+            uri = why[0]
+            if uri is None:
+                # Parse finished, but we may need the rest of the file
+                chunks.append(stream.read())
+                break
+            else:
+                return uri
+
+    content = ''.join(chunks)
+    raise MetaNotFound(content)

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/services.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/services.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/services.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/services.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,46 @@
+from openid.yadis.filters import mkFilter
+from openid.yadis.discover import discover
+from openid.yadis.etxrd import parseXRDS, iterServices
+
+def getServiceEndpoints(input_url, flt=None):
+    """Perform the Yadis protocol on the input URL and return an
+    iterable of resulting endpoint objects.
+
+    @param flt: A filter object or something that is convertable to
+        a filter object (using mkFilter) that will be used to generate
+        endpoint objects. This defaults to generating BasicEndpoint
+        objects.
+
+    @param input_url: The URL on which to perform the Yadis protocol
+
+    @return: The normalized identity URL and an iterable of endpoint
+        objects generated by the filter function.
+
+    @rtype: (str, [endpoint])
+    """
+    result = discover(input_url)
+    endpoints = applyFilter(result.normalized_uri, result.response_text, flt)
+    return (result.normalized_uri, endpoints)
+
+def applyFilter(normalized_uri, xrd_data, flt=None):
+    """Generate an iterable of endpoint objects given this input data,
+    presumably from the result of performing the Yadis protocol.
+
+    @param normalized_uri: The input URL, after following redirects,
+        as in the Yadis protocol.
+
+
+    @param xrd_data: The XML text the XRDS file fetched from the
+        normalized URI.
+    @type xrd_data: str
+
+    """
+    flt = mkFilter(flt)
+    et = parseXRDS(xrd_data)
+
+    endpoints = []
+    for service_element in iterServices(et):
+        endpoints.extend(
+            flt.getServiceEndpoints(normalized_uri, service_element))
+
+    return endpoints

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrd.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrd.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrd.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrd.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,290 @@
+from __future__ import generators
+
+__all__ = ['Service', 'ServiceParser', 'ServiceList', 'XrdsError']
+
+import xml.dom
+from servicetypes.base import GenericParser
+import warnings
+
+xrds_namespace = "xri://$xrds"
+xrd_namespace = "xri://$xrd*($v*2.0)"
+
+class XrdsError(Exception):
+    """An error with the XRDS document."""
+
+class ParseError(XrdsError):
+    def __init__(self, other):
+        self.other = other
+
+    def __str__(self):
+        return str(self.other)
+
+    def __repr__(self):
+        return '%s(%s)' % (type(self).__name__, repr(self.other))
+
+def _matchElement(node, namespace, localname):
+    return (node.nodeType == node.ELEMENT_NODE and
+            node.namespaceURI == namespace and
+            node.localName == localname)
+
+_DATA_NODE_TYPES = [xml.dom.Node.CDATA_SECTION_NODE,
+                    xml.dom.Node.TEXT_NODE]
+
+def _getContents(node):
+    chunks = []
+    for child in node.childNodes:
+        if child.nodeType in _DATA_NODE_TYPES:
+            chunks.append(child.data)
+
+    return ''.join(chunks)
+
+def _getServices(dom):
+    services = []
+
+    root = dom.documentElement
+    if _matchElement(root, xrds_namespace, 'XRDS'):
+        for node in root.childNodes:
+            if _matchElement(node, xrd_namespace, 'XRD'):
+                services.extend(_getXRDServices(node))
+
+    return services
+
+def _getXRDServices(xrd):
+    services = []
+    for node in xrd.childNodes:
+        if _matchElement(node, xrd_namespace, 'Service'):
+            services.append(Service(node))
+
+    return services
+
+def _getAttributeValue(node, namespace, attr_name):
+    prio_s = node.getAttributeNS(namespace, attr_name)
+
+    # I don't know if this is a microdom bug or we just disagree on the API,
+    # but that has an annoying tendency to return None.
+    if (not prio_s) and (not node.prefix) and (
+        node.namespaceURI == xrd_namespace):
+        prio_s = node.getAttribute(attr_name)
+
+    return prio_s
+
+def _getXRDPriority(node):
+    prio_s = _getAttributeValue(node, xrd_namespace, 'priority')
+    if prio_s:
+        return int(prio_s)
+    else:
+        return None
+
+class Service(object):
+    """I extract information from an XRD Service element.
+
+    @ivar node: A C{Service} element
+    @type node: XML DOM Element
+    """
+    def __init__(self, service_node):
+        """Adapt a node.
+
+        @param service_node: A C{Service} element
+        @type service_node: XML DOM Element
+        """
+        self.node = service_node
+
+    def priority(self):
+        """The priority defined on the Service element.
+
+        @returntype: int or NoneType"""
+        return _getXRDPriority(self.node)
+
+    def serviceTypes(self):
+        """My service types.
+
+        @returns: Generator over the URI in the Type elements.
+        @returntype: unicode
+        """
+        service_type = None
+        for node in self.node.childNodes:
+            if _matchElement(node, xrd_namespace, 'Type'):
+                yield _getContents(node)
+
+    def _serviceURIs(self):
+        """() -> [_ServiceURI]"""
+        uris = []
+        for node in self.node.childNodes:
+            if _matchElement(node, xrd_namespace, 'URI'):
+                uris.append(_ServiceURI(node))
+
+        return uris
+
+    def getExtraElements(self):
+        extras = []
+        for child in self.node.childNodes:
+            if (child.nodeType == child.ELEMENT_NODE and
+                child.namespaceURI != xrd_namespace):
+                extras.append(child)
+
+        return extras
+
+    def getElementContents(self, namespace):
+        pairs = []
+        for child in self.node.childNodes:
+            if (child.nodeType == child.ELEMENT_NODE and
+                child.namespaceURI == namespace):
+                pairs.append((child.localName, _getContents(child)))
+        return pairs
+
+class _ServiceURI(object):
+    def __init__(self, uri_node):
+        """xml.dom.Node -> NoneType"""
+        self.node = uri_node
+
+    def priority(self):
+        """() -> int or NoneType"""
+        return _getXRDPriority(self.node)
+
+    def uri(self):
+        """() -> str"""
+        return _getContents(self.node)
+
+def _prioSort(objs):
+    """Sort a list of objects all having a priority method.
+
+    The priority method should return an integer or None."""
+
+    # Find max priority
+    max_prio = None
+    for obj in objs:
+        prio = obj.priority()
+        if prio is not None and (max_prio is None or prio > max_prio):
+            max_prio = prio
+
+    if max_prio is None:
+        # There are no nodes with a set priority, so return them in
+        # the order they were given to us.
+        return objs
+
+    # Create a list of pairs of priority and obj, giving objs with
+    # no specified priority a higher priority than any other obj.
+    def getPriority(obj):
+        prio = obj.priority()
+        if prio is None:
+            prio = max_prio + 1
+
+        return (prio, obj)
+
+    prio_objs = map(getPriority, objs)
+
+    # Sort the objs and peel off the priorities. Python has a stable
+    # sort function, so ties will be in the order that they appear in
+    # the input.
+    prio_objs.sort()
+    sorted_objs = [obj for (_, obj) in prio_objs]
+
+    return sorted_objs
+
+def _getServiceURIs(doc):
+    services = _prioSort(_getServices(doc))
+
+    uris = []
+    for service in services:
+        service_uris = service._serviceURIs()
+        if not service_uris:
+            uris.append((None, service))
+
+        sorted_service_uris = _prioSort(service_uris)
+        for uri in sorted_service_uris:
+            uris.append((uri.uri(), service))
+
+    return uris
+
+
+class ServiceParser(object):
+
+    defaultParserClass = GenericParser
+
+    def __init__(self, parsers=None):
+        self.parsers = {}
+        if parsers is not None:
+            for p in parsers:
+                self.register(p)
+
+    def parse(self, xmldoc):
+        """Parse a XRDS document.
+
+        Parsing a document does not change my state, so you may use one
+        instance of me to parse as many documents as you wish.
+
+        @returns: A list of L{Service}s.
+        @returntype: L{ServiceList}
+
+        @raises XrdsError: When some required element of the document is
+            not present, i.e. C{XRDS} and C{XRD}.
+
+        @raises Exception: The underlying XML parser may raise other
+            exceptions if the document is not well-formed XML.
+            (FixMe: Should we catch these and re-cast them as XrdsErrors?)
+        """
+        from xml.dom import minidom
+        # importing this to be able to catch its exceptions, but having
+        # to know which parser its using violates the encapsulation and
+        # will probably be wrong.
+        from xml.parsers.expat import ExpatError
+        from xml.sax import SAXException
+        try:
+            domtree = minidom.parseString(xmldoc)
+        except ExpatError, e:
+            raise ParseError(e)
+        except SAXException, e:
+            raise ParseError(e)
+
+        return self.extractServices(domtree)
+
+    def extractServices(self, domtree):
+        """Extract the services in the given DOM tree that I know how
+        to parse.
+
+        XXX: document me"""
+        self._validate(domtree)
+        services = []
+        for uri, service in _getServiceURIs(domtree):
+            for stype in service.serviceTypes():
+                parser = self.parsers.get(stype, None)
+                if parser is None:
+                    parser = self.defaultParserClass(stype)
+                    self.parsers[stype] = parser
+
+                sdescriptor = parser.parse(service)
+                if uri is not None:
+                    sdescriptor.uri = uri.encode('ascii')
+                else:
+                    sdescriptor.uri = uri
+                services.append(sdescriptor)
+        return ServiceList(services)
+
+    def _validate(self, domtree):
+        root = domtree.documentElement
+        if not _matchElement(root, xrds_namespace, 'XRDS'):
+            raise XrdsError("Root element is not XRDS.")
+        for node in root.childNodes:
+            if _matchElement(node, xrd_namespace, 'XRD'):
+                break
+        else:
+            raise XrdsError("No XRD elements found.")
+
+    def register(self, parser):
+        if self.parsers.has_key(parser.type):
+            fmt = ("%s: previously registered parser %r being replaced "
+                   "for type %r by %r.")
+            warnings.warn(fmt % (self, self.parsers[parser.type],
+                                 parser.type, parser))
+        self.parsers[parser.type] = parser
+
+
+class ServiceList(object):
+    def __init__(self, services):
+        self.services = services
+
+    def __iter__(self):
+        return iter(self.services)
+
+    def getServices(self, *types):
+        return [s for s in self.services if ((not types) or (s.type in types))]

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xri.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xri.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xri.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xri.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,167 @@
+# -*- test-case-name: openid.test.test_xri -*-
+"""Utility functions for handling XRIs.
+
+@see: XRI Syntax v2.0 at the U{OASIS XRI Technical Committee<http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xri>}
+"""
+
+import re
+
+XRI_AUTHORITIES = ['!', '=', '@', '+', '$', '(']
+
+try:
+    unichr(0x10000)
+except ValueError:
+    # narrow python build
+    UCSCHAR = [
+        (0xA0, 0xD7FF),
+        (0xF900, 0xFDCF),
+        (0xFDF0, 0xFFEF),
+        ]
+
+    IPRIVATE = [
+        (0xE000, 0xF8FF),
+        ]
+else:
+    UCSCHAR = [
+        (0xA0, 0xD7FF),
+        (0xF900, 0xFDCF),
+        (0xFDF0, 0xFFEF),
+        (0x10000, 0x1FFFD),
+        (0x20000, 0x2FFFD),
+        (0x30000, 0x3FFFD),
+        (0x40000, 0x4FFFD),
+        (0x50000, 0x5FFFD),
+        (0x60000, 0x6FFFD),
+        (0x70000, 0x7FFFD),
+        (0x80000, 0x8FFFD),
+        (0x90000, 0x9FFFD),
+        (0xA0000, 0xAFFFD),
+        (0xB0000, 0xBFFFD),
+        (0xC0000, 0xCFFFD),
+        (0xD0000, 0xDFFFD),
+        (0xE1000, 0xEFFFD),
+        ]
+
+    IPRIVATE = [
+        (0xE000, 0xF8FF),
+        (0xF0000, 0xFFFFD),
+        (0x100000, 0x10FFFD),
+        ]
+
+
+_escapeme_re = re.compile('[%s]' % (''.join(
+    map(lambda (m, n): u'%s-%s' % (unichr(m), unichr(n)),
+        UCSCHAR + IPRIVATE)),))
+
+
+def identifierScheme(identifier):
+    """Determine if this identifier is an XRI or URI.
+
+    @returns: C{"XRI"} or C{"URI"}
+    """
+    if identifier.startswith('xri://') or identifier[0] in XRI_AUTHORITIES:
+        return "XRI"
+    else:
+        return "URI"
+
+
+def toIRINormal(xri):
+    """Transform an XRI to IRI-normal form."""
+    if not xri.startswith('xri://'):
+        xri = 'xri://' + xri
+    return escapeForIRI(xri)
+
+
+_xref_re = re.compile('\((.*?)\)')
+
+
+def _escape_xref(xref_match):
+    """Escape things that need to be escaped if they're in a cross-reference.
+    """
+    xref = xref_match.group()
+    xref = xref.replace('/', '%2F')
+    xref = xref.replace('?', '%3F')
+    xref = xref.replace('#', '%23')
+    return xref
+
+
+def escapeForIRI(xri):
+    """Escape things that need to be escaped when transforming to an IRI."""
+    xri = xri.replace('%', '%25')
+    xri = _xref_re.sub(_escape_xref, xri)
+    return xri
+
+
+def toURINormal(xri):
+    """Transform an XRI to URI normal form."""
+    return iriToURI(toIRINormal(xri))
+
+
+def _percentEscapeUnicode(char_match):
+    c = char_match.group()
+    return ''.join(['%%%X' % (ord(octet),) for octet in c.encode('utf-8')])
+
+
+def iriToURI(iri):
+    """Transform an IRI to a URI by escaping unicode."""
+    # According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
+    return _escapeme_re.sub(_percentEscapeUnicode, iri)
+
+
+def providerIsAuthoritative(providerID, canonicalID):
+    """Is this provider ID authoritative for this XRI?
+
+    @returntype: bool
+    """
+    # XXX: can't use rsplit until we require python >= 2.4.
+    lastbang = canonicalID.rindex('!')
+    parent = canonicalID[:lastbang]
+    return parent == providerID
+
+
+def rootAuthority(xri):
+    """Return the root authority for an XRI.
+
+    Example::
+
+        rootAuthority("xri://@example") == "xri://@"
+
+    @type xri: unicode
+    @returntype: unicode
+    """
+    if xri.startswith('xri://'):
+        xri = xri[6:]
+    authority = xri.split('/', 1)[0]
+    if authority[0] == '(':
+        # Cross-reference.
+        # XXX: This is incorrect if someone nests cross-references so there
+        #   is another close-paren in there.  Hopefully nobody does that
+        #   before we have a real xriparse function.  Hopefully nobody does
+        #   that *ever*.
+        root = authority[:authority.index(')') + 1]
+    elif authority[0] in XRI_AUTHORITIES:
+        # Other XRI reference.
+        root = authority[0]
+    else:
+        # IRI reference.  XXX: Can IRI authorities have segments?
+        segments = authority.split('!')
+        segments = reduce(list.__add__,
+            map(lambda s: s.split('*'), segments))
+        root = segments[0]
+
+    return XRI(root)
+
+
+def XRI(xri):
+    """An XRI object allowing comparison of XRI.
+
+    Ideally, this would do full normalization and provide comparsion
+    operators as per XRI Syntax.  Right now, it just does a bit of
+    canonicalization by ensuring the xri scheme is present.
+
+    @param xri: an xri string
+    @type xri: unicode
+    """
+    if not xri.startswith('xri://'):
+        xri = 'xri://' + xri
+    return xri

Added: incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrires.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrires.py?view=auto&rev=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrires.py (added)
+++ incubator/heraldry/libraries/python/openid/trunk/openid/yadis/xrires.py Wed Oct 11 16:22:33 2006
@@ -0,0 +1,120 @@
+# -*- test-case-name: openid.test.test_xrires -*-
+"""XRI resolution.
+"""
+
+from urllib import urlencode
+from openid import fetchers
+from openid.yadis import etxrd
+from openid.yadis.xri import toURINormal
+from openid.yadis.services import iterServices
+
+DEFAULT_PROXY = 'http://proxy.xri.net/'
+
+class ProxyResolver(object):
+    """Python interface to a remote XRI proxy resolver.
+    """
+    def __init__(self, proxy_url=DEFAULT_PROXY):
+        self.proxy_url = proxy_url
+
+
+    def queryURL(self, xri, service_type=None):
+        """Build a URL to query the proxy resolver.
+
+        @param xri: An XRI to resolve.
+        @type xri: unicode
+
+        @param service_type: The service type to resolve, if you desire
+            service endpoint selection.  A service type is a URI.
+        @type service_type: str
+
+        @returns: a URL
+        @returntype: str
+        """
+        # Trim off the xri:// prefix.  The proxy resolver didn't accept it
+        # when this code was written, but that may (or may not) change for
+        # XRI Resolution 2.0 Working Draft 11.
+        qxri = toURINormal(xri)[6:]
+        hxri = self.proxy_url + qxri
+        args = {
+            # XXX: If the proxy resolver will ensure that it doesn't return
+            # bogus CanonicalIDs (as per Steve's message of 15 Aug 2006
+            # 11:13:42), then we could ask for application/xrd+xml instead,
+            # which would give us a bit less to process.
+            '_xrd_r': 'application/xrds+xml',
+            }
+        if service_type:
+            args['_xrd_t'] = service_type
+        else:
+            # Don't perform service endpoint selection.
+            args['_xrd_r'] += ';sep=false'
+        query = _appendArgs(hxri, args)
+        return query
+
+
+    def query(self, xri, service_types):
+        """Resolve some services for an XRI.
+
+        Note: I don't implement any service endpoint selection beyond what
+        the resolver I'm querying does, so the Services I return may well
+        include Services that were not of the types you asked for.
+
+        May raise fetchers.HTTPFetchingError or L{etxrd.XRDError} if
+        the fetching or parsing don't go so well.
+
+        @param xri: An XRI to resolve.
+        @type xri: unicode
+
+        @param service_types: A list of services types to query for.  Service
+            types are URIs.
+        @type service_types: list of str
+
+        @returns: tuple of (CanonicalID, Service elements)
+        @returntype: (unicode, list of C{ElementTree.Element}s)
+        """
+        # FIXME: No test coverage!
+        services = []
+        # Make a seperate request to the proxy resolver for each service
+        # type, as, if it is following Refs, it could return a different
+        # XRDS for each.
+        for service_type in service_types:
+            url = self.queryURL(xri, service_type)
+            response = fetchers.fetch(url)
+            if response.status != 200:
+                # XXX: sucks to fail silently.
+                # print "response not OK:", response
+                continue
+            et = etxrd.parseXRDS(response.body)
+            canonicalID = etxrd.getCanonicalID(xri, et)
+            some_services = list(iterServices(et))
+            services.extend(some_services)
+        # TODO:
+        #  * If we do get hits for multiple service_types, we're almost
+        #    certainly going to have duplicated service entries and
+        #    broken priority ordering.
+        return canonicalID, services
+
+
+def _appendArgs(url, args):
+    """Append some arguments to an HTTP query.
+    """
+    # to be merged with oidutil.appendArgs when we combine the projects.
+    if hasattr(args, 'items'):
+        args = args.items()
+        args.sort()
+
+    if len(args) == 0:
+        return url
+
+    # According to XRI Resolution section "QXRI query parameters":
+    #
+    # """If the original QXRI had a null query component (only a leading
+    #    question mark), or a query component consisting of only question
+    #    marks, one additional leading question mark MUST be added when
+    #    adding any XRI resolution parameters."""
+
+    if '?' in url.rstrip('?'):
+        sep = '&'
+    else:
+        sep = '?'
+
+    return '%s%s%s' % (url, sep, urlencode(args))

Modified: incubator/heraldry/libraries/python/openid/trunk/setup.py
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/python/openid/trunk/setup.py?view=diff&rev=463060&r1=463059&r2=463060
==============================================================================
--- incubator/heraldry/libraries/python/openid/trunk/setup.py (original)
+++ incubator/heraldry/libraries/python/openid/trunk/setup.py Wed Oct 11 16:22:33 2006
@@ -33,6 +33,7 @@
               'openid.consumer',
               'openid.server',
               'openid.store',
+              'openid.yadis',
               ],
     license=getLicense(),
     author='JanRain',



Mime
View raw message