getting file size for all dict files to be downloaded. coming to be 400mb or so.
[worddb.git] / libs / openid / yadis / etxrd.py
blobef5cadfe102e1d2d3a7104134e3781488e1257bf
1 # -*- test-case-name: yadis.test.test_etxrd -*-
2 """
3 ElementTree interface to an XRD document.
4 """
6 __all__ = [
7 'nsTag',
8 'mkXRDTag',
9 'isXRDS',
10 'parseXRDS',
11 'getCanonicalID',
12 'getYadisXRD',
13 'getPriorityStrict',
14 'getPriority',
15 'prioSort',
16 'iterServices',
17 'expandService',
18 'expandServices',
21 import sys
22 import random
24 from datetime import datetime
25 from time import strptime
27 from openid.oidutil import importElementTree
28 ElementTree = importElementTree()
30 # the different elementtree modules don't have a common exception
31 # model. We just want to be able to catch the exceptions that signify
32 # malformed XML data and wrap them, so that the other library code
33 # doesn't have to know which XML library we're using.
34 try:
35 # Make the parser raise an exception so we can sniff out the type
36 # of exceptions
37 ElementTree.XML('> purposely malformed XML <')
38 except (SystemExit, MemoryError, AssertionError, ImportError):
39 raise
40 except:
41 XMLError = sys.exc_info()[0]
43 from openid.yadis import xri
45 class XRDSError(Exception):
46 """An error with the XRDS document."""
48 # The exception that triggered this exception
49 reason = None
53 class XRDSFraud(XRDSError):
54 """Raised when there's an assertion in the XRDS that it does not have
55 the authority to make.
56 """
60 def parseXRDS(text):
61 """Parse the given text as an XRDS document.
63 @return: ElementTree containing an XRDS document
65 @raises XRDSError: When there is a parse error or the document does
66 not contain an XRDS.
67 """
68 try:
69 element = ElementTree.XML(text)
70 except XMLError, why:
71 exc = XRDSError('Error parsing document as XML')
72 exc.reason = why
73 raise exc
74 else:
75 tree = ElementTree.ElementTree(element)
76 if not isXRDS(tree):
77 raise XRDSError('Not an XRDS document')
79 return tree
81 XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'
82 XRDS_NS = 'xri://$xrds'
84 def nsTag(ns, t):
85 return '{%s}%s' % (ns, t)
87 def mkXRDTag(t):
88 """basestring -> basestring
90 Create a tag name in the XRD 2.0 XML namespace suitable for using
91 with ElementTree
92 """
93 return nsTag(XRD_NS_2_0, t)
95 def mkXRDSTag(t):
96 """basestring -> basestring
98 Create a tag name in the XRDS XML namespace suitable for using
99 with ElementTree
101 return nsTag(XRDS_NS, t)
103 # Tags that are used in Yadis documents
104 root_tag = mkXRDSTag('XRDS')
105 service_tag = mkXRDTag('Service')
106 xrd_tag = mkXRDTag('XRD')
107 type_tag = mkXRDTag('Type')
108 uri_tag = mkXRDTag('URI')
109 expires_tag = mkXRDTag('Expires')
111 # Other XRD tags
112 canonicalID_tag = mkXRDTag('CanonicalID')
114 def isXRDS(xrd_tree):
115 """Is this document an XRDS document?"""
116 root = xrd_tree.getroot()
117 return root.tag == root_tag
119 def getYadisXRD(xrd_tree):
120 """Return the XRD element that should contain the Yadis services"""
121 xrd = None
123 # for the side-effect of assigning the last one in the list to the
124 # xrd variable
125 for xrd in xrd_tree.findall(xrd_tag):
126 pass
128 # There were no elements found, or else xrd would be set to the
129 # last one
130 if xrd is None:
131 raise XRDSError('No XRD present in tree')
133 return xrd
135 def getXRDExpiration(xrd_element, default=None):
136 """Return the expiration date of this XRD element, or None if no
137 expiration was specified.
139 @type xrd_element: ElementTree node
141 @param default: The value to use as the expiration if no
142 expiration was specified in the XRD.
144 @rtype: datetime.datetime
146 @raises ValueError: If the xrd:Expires element is present, but its
147 contents are not formatted according to the specification.
149 expires_element = xrd_element.find(expires_tag)
150 if expires_element is None:
151 return default
152 else:
153 expires_string = expires_element.text
155 # Will raise ValueError if the string is not the expected format
156 expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ")
157 return datetime(*expires_time[0:6])
159 def getCanonicalID(iname, xrd_tree):
160 """Return the CanonicalID from this XRDS document.
162 @param iname: the XRI being resolved.
163 @type iname: unicode
165 @param xrd_tree: The XRDS output from the resolver.
166 @type xrd_tree: ElementTree
168 @returns: The XRI CanonicalID or None.
169 @returntype: unicode or None
171 xrd_list = xrd_tree.findall(xrd_tag)
172 xrd_list.reverse()
174 try:
175 canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[0].text)
176 except IndexError:
177 return None
179 childID = canonicalID.lower()
181 for xrd in xrd_list[1:]:
182 # XXX: can't use rsplit until we require python >= 2.4.
183 parent_sought = childID[:childID.rindex('!')]
184 parent = xri.XRI(xrd.findtext(canonicalID_tag))
185 if parent_sought != parent.lower():
186 raise XRDSFraud("%r can not come from %s" % (childID, parent))
188 childID = parent_sought
190 root = xri.rootAuthority(iname)
191 if not xri.providerIsAuthoritative(root, childID):
192 raise XRDSFraud("%r can not come from root %r" % (childID, root))
194 return canonicalID
198 class _Max(object):
199 """Value that compares greater than any other value.
201 Should only be used as a singleton. Implemented for use as a
202 priority value for when a priority is not specified."""
203 def __cmp__(self, other):
204 if other is self:
205 return 0
207 return 1
209 Max = _Max()
211 def getPriorityStrict(element):
212 """Get the priority of this element.
214 Raises ValueError if the value of the priority is invalid. If no
215 priority is specified, it returns a value that compares greater
216 than any other value.
218 prio_str = element.get('priority')
219 if prio_str is not None:
220 prio_val = int(prio_str)
221 if prio_val >= 0:
222 return prio_val
223 else:
224 raise ValueError('Priority values must be non-negative integers')
226 # Any errors in parsing the priority fall through to here
227 return Max
229 def getPriority(element):
230 """Get the priority of this element
232 Returns Max if no priority is specified or the priority value is invalid.
234 try:
235 return getPriorityStrict(element)
236 except ValueError:
237 return Max
239 def prioSort(elements):
240 """Sort a list of elements that have priority attributes"""
241 # Randomize the services before sorting so that equal priority
242 # elements are load-balanced.
243 random.shuffle(elements)
245 prio_elems = [(getPriority(e), e) for e in elements]
246 prio_elems.sort()
247 sorted_elems = [s for (_, s) in prio_elems]
248 return sorted_elems
250 def iterServices(xrd_tree):
251 """Return an iterable over the Service elements in the Yadis XRD
253 sorted by priority"""
254 xrd = getYadisXRD(xrd_tree)
255 return prioSort(xrd.findall(service_tag))
257 def sortedURIs(service_element):
258 """Given a Service element, return a list of the contents of all
259 URI tags in priority order."""
260 return [uri_element.text for uri_element
261 in prioSort(service_element.findall(uri_tag))]
263 def getTypeURIs(service_element):
264 """Given a Service element, return a list of the contents of all
265 Type tags"""
266 return [type_element.text for type_element
267 in service_element.findall(type_tag)]
269 def expandService(service_element):
270 """Take a service element and expand it into an iterator of:
271 ([type_uri], uri, service_element)
273 uris = sortedURIs(service_element)
274 if not uris:
275 uris = [None]
277 expanded = []
278 for uri in uris:
279 type_uris = getTypeURIs(service_element)
280 expanded.append((type_uris, uri, service_element))
282 return expanded
284 def expandServices(service_elements):
285 """Take a sorted iterator of service elements and expand it into a
286 sorted iterator of:
287 ([type_uri], uri, service_element)
289 There may be more than one item in the resulting list for each
290 service element if there is more than one URI or type for a
291 service, but each triple will be unique.
293 If there is no URI or Type for a Service element, it will not
294 appear in the result.
296 expanded = []
297 for service_element in service_elements:
298 expanded.extend(expandService(service_element))
300 return expanded