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