/digsby/lib/pyxmpp/error.py
Python | 538 lines | 472 code | 13 blank | 53 comment | 19 complexity | ee624cedd583435648d7e3cbae12915a MD5 | raw file
Possible License(s): LGPL-2.1, CPL-1.0
- #
- # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU Lesser General Public License Version
- # 2.1 as published by the Free Software Foundation.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public
- # License along with this program; if not, write to the Free Software
- # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- #
- """XMPP error handling.
- Normative reference:
- - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
- - `JEP 86 <http://www.jabber.org/jeps/jep-0086.html>`__
- """
- __revision__="$Id: error.py 714 2010-04-05 10:20:10Z jajcus $"
- __docformat__="restructuredtext en"
- import libxml2
- from pyxmpp.utils import from_utf8, to_utf8
- from pyxmpp.xmlextra import common_doc, common_root, common_ns
- from pyxmpp import xmlextra
- from pyxmpp.exceptions import ProtocolError
- stream_errors={
- u"bad-format":
- ("Received XML cannot be processed",),
- u"bad-namespace-prefix":
- ("Bad namespace prefix",),
- u"conflict":
- ("Closing stream because of conflicting stream being opened",),
- u"connection-timeout":
- ("Connection was idle too long",),
- u"host-gone":
- ("Hostname is no longer hosted on the server",),
- u"host-unknown":
- ("Hostname requested is not known to the server",),
- u"improper-addressing":
- ("Improper addressing",),
- u"internal-server-error":
- ("Internal server error",),
- u"invalid-from":
- ("Invalid sender address",),
- u"invalid-id":
- ("Invalid stream ID",),
- u"invalid-namespace":
- ("Invalid namespace",),
- u"invalid-xml":
- ("Invalid XML",),
- u"not-authorized":
- ("Not authorized",),
- u"policy-violation":
- ("Local policy violation",),
- u"remote-connection-failed":
- ("Remote connection failed",),
- u"resource-constraint":
- ("Remote connection failed",),
- u"restricted-xml":
- ("Restricted XML received",),
- u"see-other-host":
- ("Redirection required",),
- u"system-shutdown":
- ("The server is being shut down",),
- u"undefined-condition":
- ("Unknown error",),
- u"unsupported-encoding":
- ("Unsupported encoding",),
- u"unsupported-stanza-type":
- ("Unsupported stanza type",),
- u"unsupported-version":
- ("Unsupported protocol version",),
- u"xml-not-well-formed":
- ("XML sent by client is not well formed",),
- }
- stanza_errors={
- u"bad-request":
- ("Bad request",
- "modify",400),
- u"conflict":
- ("Named session or resource already exists",
- "cancel",409),
- u"feature-not-implemented":
- ("Feature requested is not implemented",
- "cancel",501),
- u"forbidden":
- ("You are forbidden to perform requested action",
- "auth",403),
- u"gone":
- ("Recipient or server can no longer be contacted at this address",
- "modify",302),
- u"internal-server-error":
- ("Internal server error",
- "wait",500),
- u"item-not-found":
- ("Item not found"
- ,"cancel",404),
- u"jid-malformed":
- ("JID malformed",
- "modify",400),
- u"not-acceptable":
- ("Requested action is not acceptable",
- "modify",406),
- u"not-allowed":
- ("Requested action is not allowed",
- "cancel",405),
- u"not-authorized":
- ("Not authorized",
- "auth",401),
- u"payment-required":
- ("Payment required",
- "auth",402),
- u"recipient-unavailable":
- ("Recipient is not available",
- "wait",404),
- u"redirect":
- ("Redirection",
- "modify",302),
- u"registration-required":
- ("Registration required",
- "auth",407),
- u"remote-server-not-found":
- ("Remote server not found",
- "cancel",404),
- u"remote-server-timeout":
- ("Remote server timeout",
- "wait",504),
- u"resource-constraint":
- ("Resource constraint",
- "wait",500),
- u"service-unavailable":
- ("Service is not available",
- "cancel",503),
- u"subscription-required":
- ("Subscription is required",
- "auth",407),
- u"undefined-condition":
- ("Unknown error",
- "cancel",500),
- u"unexpected-request":
- ("Unexpected request",
- "wait",400),
- }
- legacy_codes={
- 302: "redirect",
- 400: "bad-request",
- 401: "not-authorized",
- 402: "payment-required",
- 403: "forbidden",
- 404: "item-not-found",
- 405: "not-allowed",
- 406: "not-acceptable",
- 407: "registration-required",
- 408: "remote-server-timeout",
- 409: "conflict",
- 500: "internal-server-error",
- 501: "feature-not-implemented",
- 502: "service-unavailable",
- 503: "service-unavailable",
- 504: "remote-server-timeout",
- 510: "service-unavailable",
- }
- STANZA_ERROR_NS='urn:ietf:params:xml:ns:xmpp-stanzas'
- STREAM_ERROR_NS='urn:ietf:params:xml:ns:xmpp-streams'
- PYXMPP_ERROR_NS='http://pyxmpp.jajcus.net/xmlns/errors'
- STREAM_NS="http://etherx.jabber.org/streams"
- class ErrorNode:
- """Base class for both XMPP stream and stanza errors"""
- def __init__(self,xmlnode_or_cond,ns=None,copy=True,parent=None):
- """Initialize an ErrorNode object.
- :Parameters:
- - `xmlnode_or_cond`: XML node to be wrapped into this object
- or error condition name.
- - `ns`: XML namespace URI of the error condition element (to be
- used when the provided node has no namespace).
- - `copy`: When `True` then the XML node will be copied,
- otherwise it is only borrowed.
- - `parent`: Parent node for the XML node to be copied or created.
- :Types:
- - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode`
- - `ns`: `unicode`
- - `copy`: `bool`
- - `parent`: `libxml2.xmlNode`"""
- if type(xmlnode_or_cond) is str:
- xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8")
- self.xmlnode=None
- self.borrowed=0
- if isinstance(xmlnode_or_cond,libxml2.xmlNode):
- self.__from_xml(xmlnode_or_cond,ns,copy,parent)
- elif isinstance(xmlnode_or_cond,ErrorNode):
- if not copy:
- raise TypeError, "ErrorNodes may only be copied"
- self.ns=from_utf8(xmlnode_or_cond.ns.getContent())
- self.xmlnode=xmlnode_or_cond.xmlnode.docCopyNode(common_doc,1)
- if not parent:
- parent=common_root
- parent.addChild(self.xmlnode)
- elif ns is None:
- raise ValueError, "Condition namespace not given"
- else:
- if parent:
- self.xmlnode=parent.newChild(common_ns,"error",None)
- self.borrowed=1
- else:
- self.xmlnode=common_root.newChild(common_ns,"error",None)
- cond=self.xmlnode.newChild(None,to_utf8(xmlnode_or_cond),None)
- ns=cond.newNs(ns,None)
- cond.setNs(ns)
- self.ns=from_utf8(ns.getContent())
- def __from_xml(self,xmlnode,ns,copy,parent):
- """Initialize an ErrorNode object from an XML node.
- :Parameters:
- - `xmlnode`: XML node to be wrapped into this object.
- - `ns`: XML namespace URI of the error condition element (to be
- used when the provided node has no namespace).
- - `copy`: When `True` then the XML node will be copied,
- otherwise it is only borrowed.
- - `parent`: Parent node for the XML node to be copied or created.
- :Types:
- - `xmlnode`: `libxml2.xmlNode`
- - `ns`: `unicode`
- - `copy`: `bool`
- - `parent`: `libxml2.xmlNode`"""
- if not ns:
- ns=None
- c=xmlnode.children
- while c:
- ns=c.ns().getContent()
- if ns in (STREAM_ERROR_NS,STANZA_ERROR_NS):
- break
- ns=None
- c=c.next
- if ns==None:
- raise ProtocolError, "Bad error namespace"
- self.ns=from_utf8(ns)
- if copy:
- self.xmlnode=xmlnode.docCopyNode(common_doc,1)
- if not parent:
- parent=common_root
- parent.addChild(self.xmlnode)
- else:
- self.xmlnode=xmlnode
- self.borrowed=1
- if copy:
- ns1=xmlnode.ns()
- xmlextra.replace_ns(self.xmlnode, ns1, common_ns)
- def __del__(self):
- if self.xmlnode:
- self.free()
- def free(self):
- """Free the associated XML node."""
- if not self.borrowed:
- self.xmlnode.unlinkNode()
- self.xmlnode.freeNode()
- self.xmlnode=None
- def free_borrowed(self):
- """Free the associated "borrowed" XML node."""
- self.xmlnode=None
- def is_legacy(self):
- """Check if the error node is a legacy error element.
- :return: `True` if it is a legacy error.
- :returntype: `bool`"""
- return not self.xmlnode.hasProp("type")
- def xpath_eval(self,expr,namespaces=None):
- """Evaluate XPath expression on the error element.
- The expression will be evaluated in context where the common namespace
- (the one used for stanza elements, mapped to 'jabber:client',
- 'jabber:server', etc.) is bound to prefix "ns" and other namespaces are
- bound accordingly to the `namespaces` list.
- :Parameters:
- - `expr`: the XPath expression.
- - `namespaces`: prefix to namespace mapping.
- :Types:
- - `expr`: `unicode`
- - `namespaces`: `dict`
- :return: the result of the expression evaluation.
- """
- ctxt = common_doc.xpathNewContext()
- ctxt.setContextNode(self.xmlnode)
- ctxt.xpathRegisterNs("ns",to_utf8(self.ns))
- if namespaces:
- for prefix,uri in namespaces.items():
- ctxt.xpathRegisterNs(prefix,uri)
- ret=ctxt.xpathEval(expr)
- ctxt.xpathFreeContext()
- return ret
- def get_condition(self,ns=None):
- """Get the condition element of the error.
- :Parameters:
- - `ns`: namespace URI of the condition element if it is not
- the XMPP namespace of the error element.
- :Types:
- - `ns`: `unicode`
- :return: the condition element or `None`.
- :returntype: `libxml2.xmlNode`"""
- if ns is None:
- ns=self.ns
- c=self.xpath_eval("ns:*")
- if not c:
- self.upgrade()
- c=self.xpath_eval("ns:*")
- if not c:
- return None
- if ns==self.ns and c[0].name=="text":
- if len(c)==1:
- return None
- c=c[1:]
- return c[0]
- def get_text(self):
- """Get the description text from the error element.
- :return: the text provided with the error or `None`.
- :returntype: `unicode`"""
- c=self.xpath_eval("ns:*")
- if not c:
- self.upgrade()
- t=self.xpath_eval("ns:text")
- if not t:
- return None
- return from_utf8(t[0].getContent())
- def add_custom_condition(self,ns,cond,content=None):
- """Add custom condition element to the error.
- :Parameters:
- - `ns`: namespace URI.
- - `cond`: condition name.
- - `content`: content of the element.
- :Types:
- - `ns`: `unicode`
- - `cond`: `unicode`
- - `content`: `unicode`
- :return: the new condition element.
- :returntype: `libxml2.xmlNode`"""
- c=self.xmlnode.newTextChild(None,to_utf8(cond),content)
- ns=c.newNs(to_utf8(ns),None)
- c.setNs(ns)
- return c
- def upgrade(self):
- """Upgrade a legacy error element to the XMPP compliant one.
- Use the error code provided to select the condition and the
- <error/> CDATA for the error text."""
- if not self.xmlnode.hasProp("code"):
- code=None
- else:
- try:
- code=int(self.xmlnode.prop("code"))
- except (ValueError,KeyError):
- code=None
- if code and legacy_codes.has_key(code):
- cond=legacy_codes[code]
- else:
- cond=None
- condition=self.xpath_eval("ns:*")
- if condition:
- return
- elif cond is None:
- condition=self.xmlnode.newChild(None,"undefined-condition",None)
- ns=condition.newNs(to_utf8(self.ns),None)
- condition.setNs(ns)
- condition=self.xmlnode.newChild(None,"unknown-legacy-error",None)
- ns=condition.newNs(PYXMPP_ERROR_NS,None)
- condition.setNs(ns)
- else:
- condition=self.xmlnode.newChild(None,cond,None)
- ns=condition.newNs(to_utf8(self.ns),None)
- condition.setNs(ns)
- txt=self.xmlnode.getContent()
- if txt:
- text=self.xmlnode.newTextChild(None,"text",txt)
- ns=text.newNs(to_utf8(self.ns),None)
- text.setNs(ns)
- def downgrade(self):
- """Downgrade an XMPP error element to the legacy format.
- Add a numeric code attribute according to the condition name."""
- if self.xmlnode.hasProp("code"):
- return
- cond=self.get_condition()
- if not cond:
- return
- cond=cond.name
- if stanza_errors.has_key(cond) and stanza_errors[cond][2]:
- self.xmlnode.setProp("code",to_utf8(stanza_errors[cond][2]))
- def serialize(self):
- """Serialize the element node.
- :return: serialized element in UTF-8 encoding.
- :returntype: `str`"""
- return self.xmlnode.serialize(encoding="utf-8")
- class StreamErrorNode(ErrorNode):
- """Stream error element."""
- def __init__(self,xmlnode_or_cond,copy=1,parent=None):
- """Initialize a StreamErrorNode object.
- :Parameters:
- - `xmlnode_or_cond`: XML node to be wrapped into this object
- or the primary (defined by XMPP specification) error condition name.
- - `copy`: When `True` then the XML node will be copied,
- otherwise it is only borrowed.
- - `parent`: Parent node for the XML node to be copied or created.
- :Types:
- - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode`
- - `copy`: `bool`
- - `parent`: `libxml2.xmlNode`"""
- if type(xmlnode_or_cond) is str:
- xmlnode_or_cond = xmlnode_or_cond.decode("utf-8")
- if type(xmlnode_or_cond) is unicode:
- if not stream_errors.has_key(xmlnode_or_cond):
- raise ValueError, "Bad error condition"
- ErrorNode.__init__(self,xmlnode_or_cond,STREAM_ERROR_NS,copy=copy,parent=parent)
- def get_message(self):
- """Get the message for the error.
- :return: the error message.
- :returntype: `unicode`"""
- cond=self.get_condition()
- if not cond:
- self.upgrade()
- cond=self.get_condition()
- if not cond:
- return None
- cond=cond.name
- if not stream_errors.has_key(cond):
- return None
- return stream_errors[cond][0]
- class StanzaErrorNode(ErrorNode):
- """Stanza error element."""
- def __init__(self,xmlnode_or_cond,error_type=None,copy=1,parent=None):
- """Initialize a StreamErrorNode object.
- :Parameters:
- - `xmlnode_or_cond`: XML node to be wrapped into this object
- or the primary (defined by XMPP specification) error condition name.
- - `error_type`: type of the error, one of: 'cancel', 'continue',
- 'modify', 'auth', 'wait'.
- - `copy`: When `True` then the XML node will be copied,
- otherwise it is only borrowed.
- - `parent`: Parent node for the XML node to be copied or created.
- :Types:
- - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode`
- - `error_type`: `unicode`
- - `copy`: `bool`
- - `parent`: `libxml2.xmlNode`"""
- if type(xmlnode_or_cond) is str:
- xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8")
- if type(xmlnode_or_cond) is unicode:
- if not stanza_errors.has_key(xmlnode_or_cond):
- raise ValueError, "Bad error condition"
- ErrorNode.__init__(self,xmlnode_or_cond,STANZA_ERROR_NS,copy=copy,parent=parent)
- if type(xmlnode_or_cond) is unicode:
- if error_type is None:
- error_type=stanza_errors[xmlnode_or_cond][1]
- self.xmlnode.setProp("type",to_utf8(error_type))
- def get_type(self):
- """Get the error type.
- :return: type of the error.
- :returntype: `unicode`"""
- if not self.xmlnode.hasProp("type"):
- self.upgrade()
- return from_utf8(self.xmlnode.prop("type"))
- def upgrade(self):
- """Upgrade a legacy error element to the XMPP compliant one.
- Use the error code provided to select the condition and the
- <error/> CDATA for the error text."""
- ErrorNode.upgrade(self)
- if self.xmlnode.hasProp("type"):
- return
- cond=self.get_condition().name
- if stanza_errors.has_key(cond):
- typ=stanza_errors[cond][1]
- self.xmlnode.setProp("type",typ)
- def get_message(self):
- """Get the message for the error.
- :return: the error message.
- :returntype: `unicode`"""
- cond=self.get_condition()
- if not cond:
- self.upgrade()
- cond=self.get_condition()
- if not cond:
- return None
- cond=cond.name
- if not stanza_errors.has_key(cond):
- return None
- return stanza_errors[cond][0]
- # vi: sts=4 et sw=4