From 07018c0afa7485b06424bf6787d242e7ee523d34 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 17 Dec 2009 01:54:22 +0000 Subject: [PATCH] * fixed many stanza bugs * added stanza unhandled (unhandled iqs now reply with feature-not-implemented) * added stanza exceptions (stanzas may now reply with exceptions when their handler raises an exception) --- sleekxmpp/basexmpp.py | 26 +++- sleekxmpp/componentxmpp.py | 23 --- sleekxmpp/plugins/xep_0045.py | 205 +++++++++++++++++--------- sleekxmpp/stanza/htmlim.py | 2 + sleekxmpp/stanza/iq.py | 39 ++++- sleekxmpp/stanza/message.py | 9 +- sleekxmpp/stanza/nick.py | 4 +- sleekxmpp/stanza/presence.py | 16 +- sleekxmpp/stanza/roster.py | 2 +- sleekxmpp/xmlstream/handler/waiter.py | 2 +- sleekxmpp/xmlstream/matcher/id.py | 6 + sleekxmpp/xmlstream/stanzabase.py | 37 +++-- sleekxmpp/xmlstream/xmlstream.py | 16 +- 13 files changed, 262 insertions(+), 125 deletions(-) create mode 100644 sleekxmpp/xmlstream/matcher/id.py diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 2049206..8f32c8f 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -30,6 +30,8 @@ from . stanza.message import Message from . stanza.iq import Iq from . stanza.presence import Presence from . stanza.roster import Roster +from . stanza.nick import Nick +from . stanza.htmlim import HTMLIM import logging import threading @@ -38,7 +40,6 @@ def stanzaPlugin(stanza, plugin): stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin -stanzaPlugin(Iq, Roster) class basexmpp(object): def __init__(self): @@ -61,9 +62,16 @@ class basexmpp(object): self.registerStanza(Message) self.registerStanza(Iq) self.registerStanza(Presence) + self.stanzaPlugin(Iq, Roster) + self.stanzaPlugin(Message, Nick) + self.stanzaPlugin(Message, HTMLIM) + + def stanzaPlugin(self, stanza, plugin): + stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin + stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin def Message(self, *args, **kwargs): - return Presence(self, *args, **kwargs) + return Message(self, *args, **kwargs) def Iq(self, *args, **kwargs): return Iq(self, *args, **kwargs) @@ -191,14 +199,13 @@ class basexmpp(object): message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) message['body'] = mbody message['subject'] = msubject - message['nick'] = mnick - message['html'] = mhtml + if mnick is not None: message['nick'] = mnick + if mhtml is not None: message['html'] = mhtml return message def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None): presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) - if pshow is not None: - presence['type'] = pshow + if pshow is not None: presence['type'] = pshow if pfrom is None: #maybe this should be done in stanzabase presence['from'] = self.fulljid presence['priority'] = ppriority @@ -237,7 +244,10 @@ class basexmpp(object): def _handlePresence(self, presence): """Update roster items based on presence""" self.event("presence_%s" % presence['type'], presence) - if not presence['type'] in ('available', 'unavailable'): + if presence['type'] in ('subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'): + self.event('changed_subscription', presence) + return + elif not presence['type'] in ('available', 'unavailable'): return jid = presence['from'].bare resource = presence['from'].resource @@ -260,7 +270,7 @@ class basexmpp(object): if wasoffline and show in ('available', 'away', 'xa', 'na', 'ffc'): self.event("got_online", presence) elif not wasoffline and show == 'unavailable': - self.event("got_offline", eventdata) + self.event("got_offline", presence) if len(self.roster[jid]['presence']) > 1: del self.roster[jid]['presence'][resource] else: diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index e692089..4f1a190 100755 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -64,14 +64,7 @@ class ComponentXMPP(basexmpp, XMLStream): self.server_port = port self.set_jid(jid) self.secret = secret - self.registerHandler(Callback('PresenceProbe', MatchXMLMask("" % self.default_ns), self._handlePresenceProbe)) self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) - self.registerHandler(Callback('PresenceSubscription', MatchMany(\ - (MatchXMLMask("" % self.default_ns), \ - MatchXMLMask("" % self.default_ns), \ - MatchXMLMask("" % self.default_ns), \ - MatchXMLMask("" % self.default_ns) \ - )), self._handlePresenceSubscription)) def incoming_filter(self, xmlobj): if xmlobj.tag.startswith('{jabber:client}'): @@ -80,22 +73,6 @@ class ComponentXMPP(basexmpp, XMLStream): self.incoming_filter(sub) return xmlobj - - def _handlePresenceProbe(self, stanza): - xml = stanza.xml - self.event("got_presence_probe", ({ - 'from': xml.attrib['from'], - 'to': xml.attrib['to'] - })) - - def _handlePresenceSubscription(self, presence): - xml = presence.xml - self.event("changed_subscription", { - 'type' : xml.attrib['type'], - 'from': xml.attrib['from'], - 'to': xml.attrib['to'] - }) - def start_stream_handler(self, xml): sid = xml.get('id', '') handshake = ET.Element('{jabber:component:accept}handshake') diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index 9974ef5..4b181f9 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -21,6 +21,98 @@ from __future__ import with_statement from . import base import logging from xml.etree import cElementTree as ET +from .. xmlstream.stanzabase import ElementBase, JID +from .. stanza.presence import Presence +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.matcher.xmlmask import MatchXMLMask + +class MUCPresence(ElementBase): + name = 'x' + namespace = 'http://jabber.org/protocol/muc#user' + plugin_attrib = 'muc' + interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room')) + affiliations = set(('', )) + roles = set(('', )) + + def getXMLItem(self): + item = self.xml.find('{http://jabber.org/protocol/muc#user}item') + if item is None: + item = ET.Element('{http://jabber.org/protocol/muc#user}item') + self.xml.append(item) + return item + + def getAffiliation(self): + #TODO if no affilation, set it to the default and return default + item = self.getXMLItem() + return item.get('affiliation', '') + + def setAffiliation(self, value): + item = self.getXMLItem() + #TODO check for valid affiliation + item.attrib['affiliation'] = value + return self + + def delAffiliation(self): + item = self.getXMLItem() + #TODO set default affiliation + if 'affiliation' in item.attrib: del item.attrib['affiliation'] + return self + + def getJid(self): + item = self.getXMLItem() + return JID(item.get('jid', '')) + + def setJid(self, value): + item = self.getXMLItem() + if not isinstance(value, str): + value = str(value) + item.attrib['jid'] = value + return self + + def delJid(self): + item = self.getXMLItem() + if 'jid' in item.attrib: del item.attrib['jid'] + return self + + def getRole(self): + item = self.getXMLItem() + #TODO get default role, set default role if none + return item.get('role', '') + + def setRole(self, value): + item = self.getXMLItem() + #TODO check for valid role + item.attrib['role'] = value + return self + + def delRole(self): + item = self.getXMLItem() + #TODO set default role + if 'role' in item.attrib: del item.attrib['role'] + return self + + def getNick(self): + return self.parent['from'].resource + + def getRoom(self): + return self.parent['from'].bare + + def setNick(self, value): + logging.warning("Cannot set nick through mucpresence plugin.") + return self + + def setRoom(self, value): + logging.warning("Cannot set room through mucpresence plugin.") + return self + + def delNick(self): + logging.warning("Cannot delete nick through mucpresence plugin.") + return self + + def delRoom(self): + logging.warning("Cannot delete room through mucpresence plugin.") + return self class xep_0045(base.base_plugin): """ @@ -32,74 +124,40 @@ class xep_0045(base.base_plugin): self.ourNicks = {} self.xep = '0045' self.description = 'Multi User Chat' - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handle_groupchat_message) - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handle_groupchat_presence) + # load MUC support in presence stanzas + self.xmpp.stanzaPlugin(Presence, MUCPresence) + self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_presence)) + self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_message)) - def handle_groupchat_presence(self, xml): + def handle_groupchat_presence(self, pr): """ Handle a presence in a muc. """ - source = xml.attrib['from'] - room = self.xmpp.getjidbare(source) - if room not in self.rooms.keys(): + if pr['muc']['room'] not in self.rooms.keys(): return - nick = self.xmpp.getjidresource(source) - entry = { - 'nick': nick, - 'room': room, - } - if 'type' in xml.attrib.keys(): - entry['type'] = xml.attrib['type'] + entry = pr['muc'].getValues() + if pr['type'] == 'unavailable': + self.rooms[entry['room']][entry['nick']] = None else: - entry['type'] = 'available' - for tag in ['status','show','priority']: - if xml.find(('{%s}' % self.xmpp.default_ns) + tag) != None: - entry[tag] = xml.find(('{%s}' % self.xmpp.default_ns) + tag).text - else: - entry[tag] = None - - for tag in ['affiliation','role','jid']: - item = xml.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item') - if item != None: - if tag in item.attrib: - entry[tag] = item.attrib[tag] - else: - entry[tag] = None - else: - entry[tag] = None - - if entry['status'] == 'unavailable': - self.rooms[room][nick] = None - else: - self.rooms[room][nick] = entry + self.rooms[entry['room']][entry['nick']] = entry logging.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) - self.xmpp.event("groupchat_presence", entry) + self.xmpp.event("groupchat_presence", pr) - def handle_groupchat_message(self, xml): + def handle_groupchat_message(self, msg): """ Handle a message event in a muc. """ - mfrom = xml.attrib['from'] - message = xml.find('{%s}body' % self.xmpp.default_ns).text - subject = xml.find('{%s}subject' % self.xmpp.default_ns) - if subject: - subject = subject.text - else: - subject = '' - resource = self.xmpp.getjidresource(mfrom) - mfrom = self.xmpp.getjidbare(mfrom) - mtype = xml.attrib.get('type', 'normal') - self.xmpp.event("groupchat_message", {'room': mfrom, 'name': resource, 'type': mtype, 'subject': subject, 'message': message}) + self.xmpp.event('groupchat_message', msg) def getRoomForm(self, room, ifrom=None): iq = self.xmpp.makeIqGet() - iq.attrib['to'] = room + iq['to'] = room if ifrom is not None: - iq.attrib['from'] = ifrom + iq['from'] = ifrom query = ET.Element('{http://jabber.org/protocol/muc#owner}query') iq.append(query) - result = self.xmpp.send(iq, self.xmpp.makeIq(id=iq.get('id'))) - if result.get('type', 'error') == 'error': + result = iq.send() + if result['type'] == 'error': return False - xform = result.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') + xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if xform is None: return False form = self.xmpp.plugin['xep_0004'].buildForm(xform) return form @@ -110,15 +168,16 @@ class xep_0045(base.base_plugin): #form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit') #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') iq = self.xmpp.makeIqSet() - iq.attrib['to'] = room + iq['to'] = room if ifrom is not None: - iq.attrib['from'] = ifrom + iq['from'] = ifrom query = ET.Element('{http://jabber.org/protocol/muc#owner}query') form = form.getXML('submit') query.append(form) iq.append(query) - result = self.xmpp.send(iq, self.xmpp.makeIq(iq.get('id'))) - if result.get('type', 'error') == 'error': + #result = self.xmpp.send(iq, self.xmpp.makeIq(iq.get('id'))) + result = iq.send() + if result['type'] == 'error': return False return True @@ -147,8 +206,8 @@ class xep_0045(base.base_plugin): def destroy(self, room, reason='', altroom = '', ifrom=None): iq = self.xmpp.makeIqSet() if ifrom is not None: - iq.attrib['from'] = ifrom - iq.attrib['to'] = room + iq['from'] = ifrom + iq['to'] = room query = ET.Element('{http://jabber.org/protocol/muc#owner}query') destroy = ET.Element('destroy') if altroom: @@ -158,8 +217,9 @@ class xep_0045(base.base_plugin): destroy.append(xreason) query.append(destroy) iq.append(query) - r = self.xmpp.send(iq, self.xmpp.makeIq(iq.get('id'))) - if r is None or r.get('type', 'error') == 'error': + #r = self.xmpp.send(iq, self.xmpp.makeIq(iq.get('id'))) + r = iq.send() + if r is False or r['type'] == 'error': return False return True @@ -171,17 +231,18 @@ class xep_0045(base.base_plugin): item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) query.append(item) iq = self.xmpp.makeIqSet(query) - iq.attrib['to'] = room - result = self.xmpp.send(iq, "" % iq.get('id')) - if result is None or result.get('type') != 'result': + iq['to'] = room + result = iq.send() + if result is False or result['type'] != 'result': raise ValueError return True def invite(self, room, jid, reason=''): """ Invite a jid to a room.""" - msg = self.xmpp.makeMessage(room, mtype='none') + msg = self.xmpp.makeMessage(room) + msg['from'] = self.xmpp.jid x = ET.Element('{http://jabber.org/protocol/muc#user}x') - invite = ET.Element('invite', {'to': jid}) + invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) if reason: rxml = ET.Element('reason') rxml.text = reason @@ -198,11 +259,11 @@ class xep_0045(base.base_plugin): def getRoomConfig(self, room): iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') - iq.attrib['to'] = room - result = self.xmpp.send(iq, "" % iq.get('id')) - if result is None or result.get('type') != 'result': + iq['to'] = room + result = iq.send() + if result is None or result['type'] != 'result': raise ValueError - form = result.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') + form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if form is None: raise ValueError return self.xmpp.plugin['xep_0004'].buildForm(form) @@ -212,15 +273,15 @@ class xep_0045(base.base_plugin): x = ET.Element('{jabber:x:data}x', type='cancel') query.append(x) iq = self.xmpp.makeIqSet(query) - self.xmpp.send(iq, "" % iq.get('id')) + iq.send() def setRoomConfig(self, room, config): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') x = config.getXML('submit') query.append(x) iq = self.xmpp.makeIqSet(query) - iq.attrib['to'] = room - self.xmpp.send(iq, "" % iq.get('id')) + iq['to'] = room + iq.send() def getJoinedRooms(self): return self.rooms.keys() diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py index dbfb45d..24b6859 100644 --- a/sleekxmpp/stanza/htmlim.py +++ b/sleekxmpp/stanza/htmlim.py @@ -5,6 +5,8 @@ class HTMLIM(ElementBase): name = 'html' plugin_attrib = 'html' interfaces = set(('html')) + plugin_attrib_map = set() + plugin_xml_map = set() def setHtml(self, html): if issinstance(html, str): diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index 68a429e..3961ead 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -5,7 +5,7 @@ from .. xmlstream.handler.waiter import Waiter from .. xmlstream.matcher.id import MatcherId class Iq(StanzaBase): - interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) + interfaces = set(('type', 'to', 'from', 'id','query')) types = set(('get', 'result', 'set', 'error')) name = 'iq' namespace = 'jabber:client' @@ -13,7 +13,19 @@ class Iq(StanzaBase): def __init__(self, *args, **kwargs): StanzaBase.__init__(self, *args, **kwargs) if self['id'] == '': - self['id'] = self.stream.getId() + self['id'] = self.stream.getNewId() + + def exception(self, text): + self.reply() + self['error']['condition'] = 'undefined-condition' + self['error']['text'] = text + self.send() + + def unhandled(self): + self.reply() + self['error']['condition'] = 'feature-not-implemented' + self['error']['text'] = 'No handlers registered for this request.' + self.send() def result(self): self['type'] = 'result' @@ -36,6 +48,29 @@ class Iq(StanzaBase): self.clear() StanzaBase.setPayload(self, value) + def setQuery(self, value): + query = self.xml.find("{%s}query" % value) + if query is None: + self.clear() + query = ET.Element("{%s}query" % value) + self.xml.append(query) + return self + + def getQuery(self): + for child in self.getchildren(): + if child.tag.endswith('query'): + ns =child.tag.split('}')[0] + if '{' in ns: + ns = ns[1:] + return ns + return '' + + def delQuery(self): + for child in self.getchildren(): + if child.tag.endswith('query'): + self.xml.remove(child) + return self + def unhandled(self): pass # returned unhandled error diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index 999f792..d75421c 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -4,7 +4,7 @@ from . error import Error class Message(StanzaBase): interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) - types = set((None, 'normal', 'chat', 'headline', 'error')) + types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) sub_interfaces = set(('body', 'subject')) name = 'message' namespace = 'jabber:client' @@ -22,9 +22,16 @@ class Message(StanzaBase): def reply(self, body=None): StanzaBase.reply(self) + del self['id'] if body is not None: self['body'] = body return self + def exception(self, text): + self.reply() + self['error']['condition'] = 'undefined-condition' + self['error']['text'] = text + self.send() + Message.plugin_attrib_map['error'] = Error Message.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index 438b5a9..83f7a07 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -1,10 +1,12 @@ from .. xmlstream.stanzabase import ElementBase, ET -class HTMLIM(ElementBase): +class Nick(ElementBase): namespace = 'http://jabber.org/nick/nick' name = 'nick' plugin_attrib = 'nick' interfaces = set(('nick')) + plugin_attrib_map = set() + plugin_xml_map = set() def setNick(self, nick): self.xml.text = nick diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py index f591211..cb6bd6c 100644 --- a/sleekxmpp/stanza/presence.py +++ b/sleekxmpp/stanza/presence.py @@ -19,6 +19,8 @@ class Presence(StanzaBase): if value in self.types: if show is not None: self.xml.remove(show) + if value == 'available': + value = '' self._setAttr('type', value) elif value in self.showtypes: if show is None: @@ -44,8 +46,18 @@ class Presence(StanzaBase): out = 'available' return out - def delType(self): - self.setType('available') + def reply(self): + if self['type'] == 'unsubscribe': + self['type'] = 'unsubscribed' + elif self['type'] == 'subscribe': + self['type'] = 'subscribed' + return StanzaBase.reply(self) + + def exception(self, text): + self.reply() + self['error']['condition'] = 'undefined-condition' + self['error']['text'] = text + self.send() Presence.plugin_attrib_map['error'] = Error Presence.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py index cb1c1c6..21aaa16 100644 --- a/sleekxmpp/stanza/roster.py +++ b/sleekxmpp/stanza/roster.py @@ -36,7 +36,7 @@ class Roster(ElementBase): if groupsxml is not None: for groupxml in groupsxml: item['groups'].append(groupxml.text) - items[JID(itemxml.get('jid'))] = item + items[JID(itemxml.get('jid'))] = item return items def delItems(self): diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py index 140ce44..ba29638 100644 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ b/sleekxmpp/xmlstream/handler/waiter.py @@ -20,7 +20,7 @@ class Waiter(base.BaseHandler): return self._payload.get(True, timeout) except queue.Empty: logging.warning("Timed out waiting for %s" % self.name) - return StanzaBase(stype='error') + return False def checkDelete(self): return True diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py new file mode 100644 index 0000000..ec7597d --- /dev/null +++ b/sleekxmpp/xmlstream/matcher/id.py @@ -0,0 +1,6 @@ +from . import base + +class MatcherId(base.MatcherBase): + + def match(self, xml): + return xml.get('id') == self._criteria diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 5403c69..49ddc30 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -1,4 +1,5 @@ from xml.etree import cElementTree as ET +import logging class JID(object): def __init__(self, jid): @@ -45,7 +46,12 @@ class ElementBase(object): if self.xml is None: self.xml = xml if self.xml is None: - self.xml = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) + for ename in self.name.split('/'): + new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) + if self.xml is None: + self.xml = new + else: + self.xml.append(new) if self.parent is not None: self.parent.xml.append(self.xml) return True #had to generate XML @@ -70,7 +76,7 @@ class ElementBase(object): else: return self._getAttr(attrib) elif attrib in self.plugin_attrib_map: - self.initPlugin(attrib) + if attrib not in self.plugins: self.initPlugin(attrib) return self.plugins[attrib] else: return '' @@ -87,9 +93,10 @@ class ElementBase(object): self._setAttr(attrib, value) else: self.__delitem__(attrib) - elif attrib in self.plugin_map: + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: self.initPlugin(attrib) self.initPlugin(attrib) - self.plugins[attrib].setValues(value) + self.plugins[attrib][attrib] = value return self def __delitem__(self, attrib): @@ -101,7 +108,7 @@ class ElementBase(object): return self._delSub(attrib) else: self._delAttr(attrib) - elif attrib in self.plugin_map: + elif attrib in self.plugin_attrib_map: if attrib in self.plugins: del self.plugins[attrib] return self @@ -114,7 +121,10 @@ class ElementBase(object): return True def _setAttr(self, name, value): - self.xml.attrib[name] = value + if value is None or value == '': + self.__delitem__(name) + else: + self.xml.attrib[name] = value def _delAttr(self, name): if name in self.xml.attrib: @@ -131,12 +141,14 @@ class ElementBase(object): return stanza.text def _setSubText(self, name, attrib={}, text=None): + if text is None or text == '': + return self.__delitem__(name) stanza = self.xml.find("{%s}%s" % (self.namespace, name)) if stanza is None: - self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) - stanza = self.xml.find("{%s}%s" % (self.namespace, name)) - if text is not None: - stanza.text = text + #self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) + stanza = ET.Element("{%s}%s" % (self.namespace, name)) + self.xml.append(stanza) + stanza.text = text return stanza def _delSub(self, name): @@ -228,6 +240,9 @@ class StanzaBase(ElementBase): def unhandled(self): pass + def exception(self, text): + logging.error(text) + def send(self): self.stream.sendRaw(str(self)) @@ -285,7 +300,7 @@ class StanzaBase(ElementBase): text[cc] = '>' elif c == "'": text[cc] = ''' - elif self.escape_quotes: + else: text[cc] = '"' cc += 1 return ''.join(text) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index e610764..7372903 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -262,6 +262,8 @@ class XMLStream(object): handler.prerun(stanza) self.eventqueue.put(('stanza', handler, stanza)) if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler)) + else: + stanza.unhandled() #loop through handlers and test match #spawn threads as necessary, call handlers, sending Stanza @@ -274,10 +276,18 @@ class XMLStream(object): except queue.Empty: event = None if event is not None: - etype, handler, stanza = event + etype, handler, *args = event if etype == 'stanza': - handler.run(stanza) - if etype == 'quit': + try: + handler.run(args[0]) + except: + args[0].exception(traceback.format_exc()) + elif etype == 'sched': + try: + handler.run(*args) + except: + logging.error(traceback.format_exc()) + elif etype == 'quit': logging.debug("Quitting eventRunner thread") return False