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 714 2010-04-05 10:20:10Z 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