1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18  """XMPP error handling. 
 19   
 20  Normative reference: 
 21    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__ 
 22    - `JEP 86 <http://www.jabber.org/jeps/jep-0086.html>`__ 
 23  """ 
 24   
 25  __revision__="$Id: error.py 648 2006-08-26 20:09:37Z jajcus $" 
 26  __docformat__="restructuredtext en" 
 27   
 28  import libxml2 
 29   
 30  from pyxmpp.utils import from_utf8, to_utf8 
 31  from pyxmpp.xmlextra import common_doc, common_root, common_ns 
 32  from pyxmpp import xmlextra 
 33  from pyxmpp.exceptions import ProtocolError 
 34   
 35  stream_errors={ 
 36              u"bad-format": 
 37                  ("Received XML cannot be processed",), 
 38              u"bad-namespace-prefix": 
 39                  ("Bad namespace prefix",), 
 40              u"conflict": 
 41                  ("Closing stream because of conflicting stream being opened",), 
 42              u"connection-timeout": 
 43                  ("Connection was idle too long",), 
 44              u"host-gone": 
 45                  ("Hostname is no longer hosted on the server",), 
 46              u"host-unknown": 
 47                  ("Hostname requested is not known to the server",), 
 48              u"improper-addressing": 
 49                  ("Improper addressing",), 
 50              u"internal-server-error": 
 51                  ("Internal server error",), 
 52              u"invalid-from": 
 53                  ("Invalid sender address",), 
 54              u"invalid-id": 
 55                  ("Invalid stream ID",), 
 56              u"invalid-namespace": 
 57                  ("Invalid namespace",), 
 58              u"invalid-xml": 
 59                  ("Invalid XML",), 
 60              u"not-authorized": 
 61                  ("Not authorized",), 
 62              u"policy-violation": 
 63                  ("Local policy violation",), 
 64              u"remote-connection-failed": 
 65                  ("Remote connection failed",), 
 66              u"resource-constraint": 
 67                  ("Remote connection failed",), 
 68              u"restricted-xml": 
 69                  ("Restricted XML received",), 
 70              u"see-other-host": 
 71                  ("Redirection required",), 
 72              u"system-shutdown": 
 73                  ("The server is being shut down",), 
 74              u"undefined-condition": 
 75                  ("Unknown error",), 
 76              u"unsupported-encoding": 
 77                  ("Unsupported encoding",), 
 78              u"unsupported-stanza-type": 
 79                  ("Unsupported stanza type",), 
 80              u"unsupported-version": 
 81                  ("Unsupported protocol version",), 
 82              u"xml-not-well-formed": 
 83                  ("XML sent by client is not well formed",), 
 84      } 
 85   
 86  stanza_errors={ 
 87              u"bad-request": 
 88                  ("Bad request", 
 89                  "modify",400), 
 90              u"conflict": 
 91                  ("Named session or resource already exists", 
 92                  "cancel",409), 
 93              u"feature-not-implemented": 
 94                  ("Feature requested is not implemented", 
 95                  "cancel",501), 
 96              u"forbidden": 
 97                  ("You are forbidden to perform requested action", 
 98                  "auth",403), 
 99              u"gone": 
100                  ("Recipient or server can no longer be contacted at this address", 
101                  "modify",302), 
102              u"internal-server-error": 
103                  ("Internal server error", 
104                  "wait",500), 
105              u"item-not-found": 
106                  ("Item not found" 
107                  ,"cancel",404), 
108              u"jid-malformed": 
109                  ("JID malformed", 
110                  "modify",400), 
111              u"not-acceptable": 
112                  ("Requested action is not acceptable", 
113                  "modify",406), 
114              u"not-allowed": 
115                  ("Requested action is not allowed", 
116                  "cancel",405), 
117              u"not-authorized": 
118                  ("Not authorized", 
119                  "auth",401), 
120              u"payment-required": 
121                  ("Payment required", 
122                  "auth",402), 
123              u"recipient-unavailable": 
124                  ("Recipient is not available", 
125                  "wait",404), 
126              u"redirect": 
127                  ("Redirection", 
128                  "modify",302), 
129              u"registration-required": 
130                  ("Registration required", 
131                  "auth",407), 
132              u"remote-server-not-found": 
133                  ("Remote server not found", 
134                  "cancel",404), 
135              u"remote-server-timeout": 
136                  ("Remote server timeout", 
137                  "wait",504), 
138              u"resource-constraint": 
139                  ("Resource constraint", 
140                  "wait",500), 
141              u"service-unavailable": 
142                  ("Service is not available", 
143                  "cancel",503), 
144              u"subscription-required": 
145                  ("Subscription is required", 
146                  "auth",407), 
147              u"undefined-condition": 
148                  ("Unknown error", 
149                  "cancel",500), 
150              u"unexpected-request": 
151                  ("Unexpected request", 
152                  "wait",400), 
153      } 
154   
155  legacy_codes={ 
156          302: "redirect", 
157          400: "bad-request", 
158          401: "not-authorized", 
159          402: "payment-required", 
160          403: "forbidden", 
161          404: "item-not-found", 
162          405: "not-allowed", 
163          406: "not-acceptable", 
164          407: "registration-required", 
165          408: "remote-server-timeout", 
166          409: "conflict", 
167          500: "internal-server-error", 
168          501: "feature-not-implemented", 
169          502: "service-unavailable", 
170          503: "service-unavailable", 
171          504: "remote-server-timeout", 
172          510: "service-unavailable", 
173      } 
174   
175  STANZA_ERROR_NS='urn:ietf:params:xml:ns:xmpp-stanzas' 
176  STREAM_ERROR_NS='urn:ietf:params:xml:ns:xmpp-streams' 
177  PYXMPP_ERROR_NS='http://pyxmpp.jajcus.net/xmlns/errors' 
178  STREAM_NS="http://etherx.jabber.org/streams" 
179   
181      """Base class for both XMPP stream and stanza errors""" 
182 -    def __init__(self,xmlnode_or_cond,ns=None,copy=True,parent=None): 
 183          """Initialize an ErrorNode object. 
184   
185          :Parameters: 
186              - `xmlnode_or_cond`: XML node to be wrapped into this object 
187                or error condition name. 
188              - `ns`: XML namespace URI of the error condition element (to be 
189                used when the provided node has no namespace). 
190              - `copy`: When `True` then the XML node will be copied, 
191                otherwise it is only borrowed. 
192              - `parent`: Parent node for the XML node to be copied or created. 
193          :Types: 
194              - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode` 
195              - `ns`: `unicode` 
196              - `copy`: `bool` 
197              - `parent`: `libxml2.xmlNode`""" 
198          if type(xmlnode_or_cond) is str: 
199              xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8") 
200          self.xmlnode=None 
201          self.borrowed=0 
202          if isinstance(xmlnode_or_cond,libxml2.xmlNode): 
203              self.__from_xml(xmlnode_or_cond,ns,copy,parent) 
204          elif isinstance(xmlnode_or_cond,ErrorNode): 
205              if not copy: 
206                  raise TypeError, "ErrorNodes may only be copied" 
207              self.ns=from_utf8(xmlnode_or_cond.ns.getContent()) 
208              self.xmlnode=xmlnode_or_cond.xmlnode.docCopyNode(common_doc,1) 
209              if not parent: 
210                  parent=common_root 
211              parent.addChild(self.xmlnode) 
212          elif ns is None: 
213              raise ValueError, "Condition namespace not given" 
214          else: 
215              if parent: 
216                  self.xmlnode=parent.newChild(common_ns,"error",None) 
217                  self.borrowed=1 
218              else: 
219                  self.xmlnode=common_root.newChild(common_ns,"error",None) 
220              cond=self.xmlnode.newChild(None,to_utf8(xmlnode_or_cond),None) 
221              ns=cond.newNs(ns,None) 
222              cond.setNs(ns) 
223              self.ns=from_utf8(ns.getContent()) 
 224   
226          """Initialize an ErrorNode object from an XML node. 
227   
228          :Parameters: 
229              - `xmlnode`: XML node to be wrapped into this object. 
230              - `ns`: XML namespace URI of the error condition element (to be 
231                used when the provided node has no namespace). 
232              - `copy`: When `True` then the XML node will be copied, 
233                otherwise it is only borrowed. 
234              - `parent`: Parent node for the XML node to be copied or created. 
235          :Types: 
236              - `xmlnode`: `libxml2.xmlNode` 
237              - `ns`: `unicode` 
238              - `copy`: `bool` 
239              - `parent`: `libxml2.xmlNode`""" 
240          if not ns: 
241              ns=None 
242              c=xmlnode.children 
243              while c: 
244                  ns=c.ns().getContent() 
245                  if ns in (STREAM_ERROR_NS,STANZA_ERROR_NS): 
246                      break 
247                  ns=None 
248                  c=c.next 
249              if ns==None: 
250                  raise ProtocolError, "Bad error namespace" 
251          self.ns=from_utf8(ns) 
252          if copy: 
253              self.xmlnode=xmlnode.docCopyNode(common_doc,1) 
254              if not parent: 
255                  parent=common_root 
256              parent.addChild(self.xmlnode) 
257          else: 
258              self.xmlnode=xmlnode 
259              self.borrowed=1 
260          if copy: 
261              ns1=xmlnode.ns() 
262              xmlextra.replace_ns(self.xmlnode, ns1, common_ns) 
 263   
265          if self.xmlnode: 
266              self.free() 
 267   
269          """Free the associated XML node.""" 
270          if not self.borrowed: 
271              self.xmlnode.unlinkNode() 
272              self.xmlnode.freeNode() 
273          self.xmlnode=None 
 274   
276          """Free the associated "borrowed" XML node.""" 
277          self.xmlnode=None 
 278   
280          """Check if the error node is a legacy error element. 
281   
282          :return: `True` if it is a legacy error. 
283          :returntype: `bool`""" 
284          return not self.xmlnode.hasProp("type") 
 285   
287          """Evaluate XPath expression on the error element. 
288   
289          The expression will be evaluated in context where the common namespace 
290          (the one used for stanza elements, mapped to 'jabber:client', 
291          'jabber:server', etc.) is bound to prefix "ns" and other namespaces are 
292          bound accordingly to the `namespaces` list. 
293   
294          :Parameters: 
295              - `expr`: the XPath expression. 
296              - `namespaces`: prefix to namespace mapping. 
297          :Types: 
298              - `expr`: `unicode` 
299              - `namespaces`: `dict` 
300   
301          :return: the result of the expression evaluation. 
302          """ 
303          ctxt = common_doc.xpathNewContext() 
304          ctxt.setContextNode(self.xmlnode) 
305          ctxt.xpathRegisterNs("ns",to_utf8(self.ns)) 
306          if namespaces: 
307              for prefix,uri in namespaces.items(): 
308                  ctxt.xpathRegisterNs(prefix,uri) 
309          ret=ctxt.xpathEval(expr) 
310          ctxt.xpathFreeContext() 
311          return ret 
 312   
314          """Get the condition element of the error. 
315   
316          :Parameters: 
317              - `ns`: namespace URI of the condition element if it is not 
318                the XMPP namespace of the error element. 
319          :Types: 
320              - `ns`: `unicode` 
321   
322          :return: the condition element or `None`. 
323          :returntype: `libxml2.xmlNode`""" 
324          if ns is None: 
325              ns=self.ns 
326          c=self.xpath_eval("ns:*") 
327          if not c: 
328              self.upgrade() 
329              c=self.xpath_eval("ns:*") 
330          if not c: 
331              return None 
332          if ns==self.ns and c[0].name=="text": 
333              if len(c)==1: 
334                  return None 
335              c=c[1:] 
336          return c[0] 
 337   
338 -    def get_text(self): 
 339          """Get the description text from the error element. 
340   
341          :return: the text provided with the error or `None`. 
342          :returntype: `unicode`""" 
343          c=self.xpath_eval("ns:*") 
344          if not c: 
345              self.upgrade() 
346          t=self.xpath_eval("ns:text") 
347          if not t: 
348              return None 
349          return from_utf8(t[0].getContent()) 
 350   
352          """Add custom condition element to the error. 
353   
354          :Parameters: 
355              - `ns`: namespace URI. 
356              - `cond`: condition name. 
357              - `content`: content of the element. 
358   
359          :Types: 
360              - `ns`: `unicode` 
361              - `cond`: `unicode` 
362              - `content`: `unicode` 
363   
364          :return: the new condition element. 
365          :returntype: `libxml2.xmlNode`""" 
366          c=self.xmlnode.newTextChild(None,to_utf8(cond),content) 
367          ns=c.newNs(to_utf8(ns),None) 
368          c.setNs(ns) 
369          return c 
 370   
372          """Upgrade a legacy error element to the XMPP compliant one. 
373   
374          Use the error code provided to select the condition and the 
375          <error/> CDATA for the error text.""" 
376   
377          if not self.xmlnode.hasProp("code"): 
378              code=None 
379          else: 
380              try: 
381                  code=int(self.xmlnode.prop("code")) 
382              except (ValueError,KeyError): 
383                  code=None 
384   
385          if code and legacy_codes.has_key(code): 
386              cond=legacy_codes[code] 
387          else: 
388              cond=None 
389   
390          condition=self.xpath_eval("ns:*") 
391          if condition: 
392              return 
393          elif cond is None: 
394              condition=self.xmlnode.newChild(None,"undefined-condition",None) 
395              ns=condition.newNs(to_utf8(self.ns),None) 
396              condition.setNs(ns) 
397              condition=self.xmlnode.newChild(None,"unknown-legacy-error",None) 
398              ns=condition.newNs(PYXMPP_ERROR_NS,None) 
399              condition.setNs(ns) 
400          else: 
401              condition=self.xmlnode.newChild(None,cond,None) 
402              ns=condition.newNs(to_utf8(self.ns),None) 
403              condition.setNs(ns) 
404          txt=self.xmlnode.getContent() 
405          if txt: 
406              text=self.xmlnode.newTextChild(None,"text",txt) 
407              ns=text.newNs(to_utf8(self.ns),None) 
408              text.setNs(ns) 
 409   
411          """Downgrade an XMPP error element to the legacy format. 
412   
413          Add a numeric code attribute according to the condition name.""" 
414          if self.xmlnode.hasProp("code"): 
415              return 
416          cond=self.get_condition() 
417          if not cond: 
418              return 
419          cond=cond.name 
420          if stanza_errors.has_key(cond) and stanza_errors[cond][2]: 
421              self.xmlnode.setProp("code",to_utf8(stanza_errors[cond][2])) 
 422   
424          """Serialize the element node. 
425   
426          :return: serialized element in UTF-8 encoding. 
427          :returntype: `str`""" 
428          return self.xmlnode.serialize(encoding="utf-8") 
  429   
431      """Stream error element.""" 
432 -    def __init__(self,xmlnode_or_cond,copy=1,parent=None): 
 433          """Initialize a StreamErrorNode object. 
434   
435          :Parameters: 
436              - `xmlnode_or_cond`: XML node to be wrapped into this object 
437                or the primary (defined by XMPP specification) error condition name. 
438              - `copy`: When `True` then the XML node will be copied, 
439                otherwise it is only borrowed. 
440              - `parent`: Parent node for the XML node to be copied or created. 
441          :Types: 
442              - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode` 
443              - `copy`: `bool` 
444              - `parent`: `libxml2.xmlNode`""" 
445          if type(xmlnode_or_cond) is str: 
446              xmlnode_or_cond = xmlnode_or_cond.decode("utf-8") 
447          if type(xmlnode_or_cond) is unicode: 
448              if not stream_errors.has_key(xmlnode_or_cond): 
449                  raise ValueError, "Bad error condition" 
450          ErrorNode.__init__(self,xmlnode_or_cond,STREAM_ERROR_NS,copy=copy,parent=parent) 
 451   
453          """Get the message for the error. 
454   
455          :return: the error message. 
456          :returntype: `unicode`""" 
457          cond=self.get_condition() 
458          if not cond: 
459              self.upgrade() 
460              cond=self.get_condition() 
461              if not cond: 
462                  return None 
463          cond=cond.name 
464          if not stream_errors.has_key(cond): 
465              return None 
466          return stream_errors[cond][0] 
  467   
469      """Stanza error element.""" 
470 -    def __init__(self,xmlnode_or_cond,error_type=None,copy=1,parent=None): 
 471          """Initialize a StreamErrorNode object. 
472   
473          :Parameters: 
474              - `xmlnode_or_cond`: XML node to be wrapped into this object 
475                or the primary (defined by XMPP specification) error condition name. 
476              - `error_type`: type of the error, one of: 'cancel', 'continue', 
477                'modify', 'auth', 'wait'. 
478              - `copy`: When `True` then the XML node will be copied, 
479                otherwise it is only borrowed. 
480              - `parent`: Parent node for the XML node to be copied or created. 
481          :Types: 
482              - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode` 
483              - `error_type`: `unicode` 
484              - `copy`: `bool` 
485              - `parent`: `libxml2.xmlNode`""" 
486          if type(xmlnode_or_cond) is str: 
487              xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8") 
488          if type(xmlnode_or_cond) is unicode: 
489              if not stanza_errors.has_key(xmlnode_or_cond): 
490                  raise ValueError, "Bad error condition" 
491   
492          ErrorNode.__init__(self,xmlnode_or_cond,STANZA_ERROR_NS,copy=copy,parent=parent) 
493   
494          if type(xmlnode_or_cond) is unicode: 
495              if error_type is None: 
496                  error_type=stanza_errors[xmlnode_or_cond][1] 
497              self.xmlnode.setProp("type",to_utf8(error_type)) 
 498   
500          """Get the error type. 
501   
502          :return: type of the error. 
503          :returntype: `unicode`""" 
504          if not self.xmlnode.hasProp("type"): 
505              self.upgrade() 
506          return from_utf8(self.xmlnode.prop("type")) 
 507   
509          """Upgrade a legacy error element to the XMPP compliant one. 
510   
511          Use the error code provided to select the condition and the 
512          <error/> CDATA for the error text.""" 
513          ErrorNode.upgrade(self) 
514          if self.xmlnode.hasProp("type"): 
515              return 
516   
517          cond=self.get_condition().name 
518          if stanza_errors.has_key(cond): 
519              typ=stanza_errors[cond][1] 
520              self.xmlnode.setProp("type",typ) 
 521   
523          """Get the message for the error. 
524   
525          :return: the error message. 
526          :returntype: `unicode`""" 
527          cond=self.get_condition() 
528          if not cond: 
529              self.upgrade() 
530              cond=self.get_condition() 
531              if not cond: 
532                  return None 
533          cond=cond.name 
534          if not stanza_errors.has_key(cond): 
535              return None 
536          return stanza_errors[cond][0] 
  537   
538   
539