diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 61aaeff..02f775a 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -13,9 +13,9 @@ import copy import logging import sleekxmpp -from sleekxmpp import plugins +from sleekxmpp import plugins, roster +from sleekxmpp.exceptions import IqError, IqTimeout -import sleekxmpp.roster as roster from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError from sleekxmpp.stanza.roster import Roster from sleekxmpp.stanza.nick import Nick @@ -743,6 +743,29 @@ class BaseXMPP(XMLStream): self.event("changed_status", presence) + def exception(self, exception): + """ + Process any uncaught exceptions, notably IqError and + IqTimeout exceptions. + + Overrides XMLStream.exception. + + Arguments: + exception -- An unhandled exception object. + """ + if isinstance(exception, IqError): + iq = exception.iq + log.error('%s: %s' % (iq['error']['condition'], + iq['error']['text'])) + log.warning('You should catch IqError exceptions') + elif isinstance(exception, IqTimeout): + iq = exception.iq + log.error('Request timed out: %s' % iq) + log.warning('You should catch IqTimeout exceptions') + else: + log.exception(exception) + + # Restore the old, lowercased name for backwards compatibility. basexmpp = BaseXMPP diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py index 49d0f94..61d24f6 100644 --- a/sleekxmpp/exceptions.py +++ b/sleekxmpp/exceptions.py @@ -20,9 +20,9 @@ class XMPPError(Exception): Meant for use in SleekXMPP plugins and applications using SleekXMPP. """ - def __init__(self, condition='undefined-condition', text=None, etype=None, - extension=None, extension_ns=None, extension_args=None, - clear=True): + def __init__(self, condition='undefined-condition', text=None, + etype='cancel', extension=None, extension_ns=None, + extension_args=None, clear=True): """ Create a new XMPPError exception. @@ -31,8 +31,10 @@ class XMPPError(Exception): Arguments: condition -- The XMPP defined error condition. + Defaults to 'undefined-condition'. text -- Human readable text describing the error. etype -- The XMPP error type, such as cancel or modify. + Defaults to 'cancel'. extension -- Tag name of the extension's XML content. extension_ns -- XML namespace of the extensions' XML content. extension_args -- Content and attributes for the extension @@ -54,7 +56,7 @@ class XMPPError(Exception): self.extension_args = extension_args -class IqTimeout(Exception): +class IqTimeout(XMPPError): """ An exception which indicates that an IQ request response has not been @@ -62,10 +64,13 @@ class IqTimeout(Exception): """ def __init__(self, iq): + super(IqTimeout, self).__init__( + condition='remote-server-timeout', + etype='cancel') + self.iq = iq - -class IqError(Exception): +class IqError(XMPPError): """ An exception raised when an Iq stanza of type 'error' is received @@ -73,4 +78,9 @@ class IqError(Exception): """ def __init__(self, iq): + super(IqError, self).__init__( + condition=iq['error']['condition'], + text=iq['error']['text'], + etype=iq['error']['type']) + self.iq = iq diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index 9e1d1cf..470a122 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -10,7 +10,7 @@ import logging import traceback import sys -from sleekxmpp.exceptions import XMPPError +from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout from sleekxmpp.stanza import Error from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin @@ -43,23 +43,41 @@ class RootStanza(StanzaBase): Arguments: e -- Exception object """ - if isinstance(e, XMPPError): - self.reply(clear=e.clear) + if isinstance(e, IqError): + # We received an Iq error reply, but it wasn't caught + # locally. Using the condition/text from that error + # response could leak too much information, so we'll + # only use a generic error here. + self.reply() + self['error']['condition'] = 'undefined-condition' + self['error']['text'] = 'External error' + self['error']['type'] = 'cancel' + log.warning('You should catch IqError exceptions') + self.send() + elif isinstance(e, IqTimeout): + self.reply() + self['error']['condition'] = 'remote-server-timeout' + self['error']['type'] = 'wait' + log.warning('You should catch IqTimeout exceptions') + self.send() + elif isinstance(e, XMPPError): # We raised this deliberately + self.reply(clear=e.clear) self['error']['condition'] = e.condition self['error']['text'] = e.text + self['error']['type'] = e.etype if e.extension is not None: # Extended error tag extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) self['error'].append(extxml) - self['error']['type'] = e.etype self.send() else: - self.reply() # We probably didn't raise this on purpose, so send an error stanza + self.reply() self['error']['condition'] = 'undefined-condition' self['error']['text'] = "SleekXMPP got into trouble." + self['error']['type'] = 'cancel' self.send() # log the error log.exception('Error handling {%s}%s stanza' %