Package pyxmpp :: Module error
[hide private]

Source Code for Module pyxmpp.error

  1  # 
  2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net> 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU Lesser General Public License Version 
  6  # 2.1 as published by the Free Software Foundation. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, 
  9  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 11  # GNU Lesser General Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU Lesser General Public 
 14  # License along with this program; if not, write to the Free Software 
 15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 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   
179 -class ErrorNode:
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
224 - def __from_xml(self,xmlnode,ns,copy,parent):
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
263 - def __del__(self):
264 if self.xmlnode: 265 self.free()
266
267 - def free(self):
268 """Free the associated XML node.""" 269 if not self.borrowed: 270 self.xmlnode.unlinkNode() 271 self.xmlnode.freeNode() 272 self.xmlnode=None
273
274 - def free_borrowed(self):
275 """Free the associated "borrowed" XML node.""" 276 self.xmlnode=None
277
278 - def is_legacy(self):
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
285 - def xpath_eval(self,expr,namespaces=None):
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
312 - def get_condition(self,ns=None):
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
350 - def add_custom_condition(self,ns,cond,content=None):
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
370 - def upgrade(self):
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
409 - def downgrade(self):
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
422 - def serialize(self):
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
429 -class StreamErrorNode(ErrorNode):
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
451 - def get_message(self):
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
467 -class StanzaErrorNode(ErrorNode):
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
498 - def get_type(self):
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
507 - def upgrade(self):
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
521 - def get_message(self):
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 # vi: sts=4 et sw=4 538