PageRenderTime 189ms CodeModel.GetById 161ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/digsby/lib/pyxmpp/error.py

https://github.com/xiaohangyu/digsby
Python | 538 lines | 472 code | 13 blank | 53 comment | 20 complexity | ee624cedd583435648d7e3cbae12915a MD5 | raw file
  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
 20Normative 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
 28import libxml2
 29
 30from pyxmpp.utils import from_utf8, to_utf8
 31from pyxmpp.xmlextra import common_doc, common_root, common_ns
 32from pyxmpp import xmlextra
 33from pyxmpp.exceptions import ProtocolError
 34
 35stream_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
 86stanza_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
155legacy_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
175STANZA_ERROR_NS='urn:ietf:params:xml:ns:xmpp-stanzas'
176STREAM_ERROR_NS='urn:ietf:params:xml:ns:xmpp-streams'
177PYXMPP_ERROR_NS='http://pyxmpp.jajcus.net/xmlns/errors'
178STREAM_NS="http://etherx.jabber.org/streams"
179
180class 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
430class 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
468class 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