1   
  2  """ 
  3  ElementTree interface to an XRD document. 
  4  """ 
  5   
  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', 
 19      ] 
 20   
 21  import sys 
 22  import random 
 23   
 24  from datetime import datetime 
 25  from time import strptime 
 26   
 27  from openid.oidutil import importElementTree 
 28  ElementTree = importElementTree() 
 29   
 30   
 31   
 32   
 33   
 34  try: 
 35       
 36       
 37      ElementTree.XML('> purposely malformed XML <') 
 38  except (SystemExit, MemoryError, AssertionError, ImportError): 
 39      raise 
 40  except: 
 41      XMLError = sys.exc_info()[0] 
 42   
 43  from openid.yadis import xri 
 44   
 46      """An error with the XRDS document.""" 
 47   
 48       
 49      reason = None 
  50   
 51   
 52   
 54      """Raised when there's an assertion in the XRDS that it does not have 
 55      the authority to make. 
 56      """ 
  57   
 58   
 59   
 61      """Parse the given text as an XRDS document. 
 62   
 63      @return: ElementTree containing an XRDS document 
 64   
 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') 
 78   
 79          return tree 
  80   
 81  XRD_NS_2_0 = 'xri://$xrd*($v*2.0)' 
 82  XRDS_NS = 'xri://$xrds' 
 83   
 85      return '{%s}%s' % (ns, t) 
  86   
 88      """basestring -> basestring 
 89   
 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) 
  94   
 96      """basestring -> basestring 
 97   
 98      Create a tag name in the XRDS XML namespace suitable for using 
 99      with ElementTree 
100      """ 
101      return nsTag(XRDS_NS, t) 
 102   
103   
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') 
110   
111   
112  canonicalID_tag = mkXRDTag('CanonicalID') 
113   
115      """Is this document an XRDS document?""" 
116      root = xrd_tree.getroot() 
117      return root.tag == root_tag 
 118   
120      """Return the XRD element that should contain the Yadis services""" 
121      xrd = None 
122   
123       
124       
125      for xrd in xrd_tree.findall(xrd_tag): 
126          pass 
127   
128       
129       
130      if xrd is None: 
131          raise XRDSError('No XRD present in tree') 
132   
133      return xrd 
 134   
136      """Return the expiration date of this XRD element, or None if no 
137      expiration was specified. 
138   
139      @type xrd_element: ElementTree node 
140   
141      @param default: The value to use as the expiration if no 
142          expiration was specified in the XRD. 
143   
144      @rtype: datetime.datetime 
145   
146      @raises ValueError: If the xrd:Expires element is present, but its 
147          contents are not formatted according to the specification. 
148      """ 
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 
154   
155           
156          expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ") 
157          return datetime(*expires_time[0:6]) 
 158   
160      """Return the CanonicalID from this XRDS document. 
161   
162      @param iname: the XRI being resolved. 
163      @type iname: unicode 
164   
165      @param xrd_tree: The XRDS output from the resolver. 
166      @type xrd_tree: ElementTree 
167   
168      @returns: The XRI CanonicalID or None. 
169      @returntype: unicode or None 
170      """ 
171      xrd_list = xrd_tree.findall(xrd_tag) 
172      xrd_list.reverse() 
173   
174      try: 
175          canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[0].text) 
176      except IndexError: 
177          return None 
178   
179      childID = canonicalID.lower() 
180   
181      for xrd in xrd_list[1:]: 
182           
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)) 
187   
188          childID = parent_sought 
189   
190      root = xri.rootAuthority(iname) 
191      if not xri.providerIsAuthoritative(root, childID): 
192          raise XRDSFraud("%r can not come from root %r" % (childID, root)) 
193   
194      return canonicalID 
 195   
196   
197   
199      """Value that compares greater than any other value. 
200   
201      Should only be used as a singleton. Implemented for use as a 
202      priority value for when a priority is not specified.""" 
204          if other is self: 
205              return 0 
206   
207          return 1 
  208   
209  Max = _Max() 
210   
212      """Get the priority of this element. 
213   
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. 
217      """ 
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') 
225   
226       
227      return Max 
 228   
230      """Get the priority of this element 
231   
232      Returns Max if no priority is specified or the priority value is invalid. 
233      """ 
234      try: 
235          return getPriorityStrict(element) 
236      except ValueError: 
237          return Max 
 238   
240      """Sort a list of elements that have priority attributes""" 
241       
242       
243      random.shuffle(elements) 
244   
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 
 249   
251      """Return an iterable over the Service elements in the Yadis XRD 
252   
253      sorted by priority""" 
254      xrd = getYadisXRD(xrd_tree) 
255      return prioSort(xrd.findall(service_tag)) 
 256   
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))] 
 262   
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)] 
 268   
270      """Take a service element and expand it into an iterator of: 
271      ([type_uri], uri, service_element) 
272      """ 
273      uris = sortedURIs(service_element) 
274      if not uris: 
275          uris = [None] 
276   
277      expanded = [] 
278      for uri in uris: 
279          type_uris = getTypeURIs(service_element) 
280          expanded.append((type_uris, uri, service_element)) 
281   
282      return expanded 
 283   
285      """Take a sorted iterator of service elements and expand it into a 
286      sorted iterator of: 
287      ([type_uri], uri, service_element) 
288   
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. 
292   
293      If there is no URI or Type for a Service element, it will not 
294      appear in the result. 
295      """ 
296      expanded = [] 
297      for service_element in service_elements: 
298          expanded.extend(expandService(service_element)) 
299   
300      return expanded 
 301