Package pyxmpp :: Module error
[hide private]

Source Code for Module pyxmpp.error

  1  # 
  2  # (C) Copyright 2003-2006 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  __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   
180 -class ErrorNode:
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
225 - def __from_xml(self,xmlnode,ns,copy,parent):
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
264 - def __del__(self):
265 if self.xmlnode: 266 self.free()
267
268 - def free(self):
269 """Free the associated XML node.""" 270 if not self.borrowed: 271 self.xmlnode.unlinkNode() 272 self.xmlnode.freeNode() 273 self.xmlnode=None
274
275 - def free_borrowed(self):
276 """Free the associated "borrowed" XML node.""" 277 self.xmlnode=None
278
279 - def is_legacy(self):
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
286 - def xpath_eval(self,expr,namespaces=None):
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
313 - def get_condition(self,ns=None):
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
351 - def add_custom_condition(self,ns,cond,content=None):
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
371 - def upgrade(self):
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
410 - def downgrade(self):
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
423 - def serialize(self):
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
430 -class StreamErrorNode(ErrorNode):
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
452 - def get_message(self):
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
468 -class StanzaErrorNode(ErrorNode):
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
499 - def get_type(self):
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
508 - def upgrade(self):
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
522 - def get_message(self):
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 # vi: sts=4 et sw=4 539