Fix bug in XEP-0030 plugin.

xep_0030 still referenced event_handlers. Added the method event_handled
which will return the number of registered handlers for an event to
resolve the issue.
This commit is contained in:
Lance Stout 2010-10-31 18:27:52 -04:00
parent 973890e2c9
commit 9e248bb852
2 changed files with 272 additions and 262 deletions

View file

@ -14,314 +14,312 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq from .. stanza.iq import Iq
class DiscoInfo(ElementBase): class DiscoInfo(ElementBase):
namespace = 'http://jabber.org/protocol/disco#info' namespace = 'http://jabber.org/protocol/disco#info'
name = 'query' name = 'query'
plugin_attrib = 'disco_info' plugin_attrib = 'disco_info'
interfaces = set(('node', 'features', 'identities')) interfaces = set(('node', 'features', 'identities'))
def getFeatures(self): def getFeatures(self):
features = [] features = []
featuresXML = self.xml.findall('{%s}feature' % self.namespace) featuresXML = self.xml.findall('{%s}feature' % self.namespace)
for feature in featuresXML: for feature in featuresXML:
features.append(feature.attrib['var']) features.append(feature.attrib['var'])
return features return features
def setFeatures(self, features): def setFeatures(self, features):
self.delFeatures() self.delFeatures()
for name in features: for name in features:
self.addFeature(name) self.addFeature(name)
def delFeatures(self): def delFeatures(self):
featuresXML = self.xml.findall('{%s}feature' % self.namespace) featuresXML = self.xml.findall('{%s}feature' % self.namespace)
for feature in featuresXML: for feature in featuresXML:
self.xml.remove(feature) self.xml.remove(feature)
def addFeature(self, feature): def addFeature(self, feature):
featureXML = ET.Element('{%s}feature' % self.namespace, featureXML = ET.Element('{%s}feature' % self.namespace,
{'var': feature}) {'var': feature})
self.xml.append(featureXML) self.xml.append(featureXML)
def delFeature(self, feature): def delFeature(self, feature):
featuresXML = self.xml.findall('{%s}feature' % self.namespace) featuresXML = self.xml.findall('{%s}feature' % self.namespace)
for featureXML in featuresXML: for featureXML in featuresXML:
if featureXML.attrib['var'] == feature: if featureXML.attrib['var'] == feature:
self.xml.remove(featureXML) self.xml.remove(featureXML)
def getIdentities(self): def getIdentities(self):
ids = [] ids = []
idsXML = self.xml.findall('{%s}identity' % self.namespace) idsXML = self.xml.findall('{%s}identity' % self.namespace)
for idXML in idsXML: for idXML in idsXML:
idData = (idXML.attrib['category'], idData = (idXML.attrib['category'],
idXML.attrib['type'], idXML.attrib['type'],
idXML.attrib.get('name', '')) idXML.attrib.get('name', ''))
ids.append(idData) ids.append(idData)
return ids return ids
def setIdentities(self, ids): def setIdentities(self, ids):
self.delIdentities() self.delIdentities()
for idData in ids: for idData in ids:
self.addIdentity(*idData) self.addIdentity(*idData)
def delIdentities(self): def delIdentities(self):
idsXML = self.xml.findall('{%s}identity' % self.namespace) idsXML = self.xml.findall('{%s}identity' % self.namespace)
for idXML in idsXML: for idXML in idsXML:
self.xml.remove(idXML) self.xml.remove(idXML)
def addIdentity(self, category, id_type, name=''): def addIdentity(self, category, id_type, name=''):
idXML = ET.Element('{%s}identity' % self.namespace, idXML = ET.Element('{%s}identity' % self.namespace,
{'category': category, {'category': category,
'type': id_type, 'type': id_type,
'name': name}) 'name': name})
self.xml.append(idXML) self.xml.append(idXML)
def delIdentity(self, category, id_type, name=''): def delIdentity(self, category, id_type, name=''):
idsXML = self.xml.findall('{%s}identity' % self.namespace) idsXML = self.xml.findall('{%s}identity' % self.namespace)
for idXML in idsXML: for idXML in idsXML:
idData = (idXML.attrib['category'], idData = (idXML.attrib['category'],
idXML.attrib['type']) idXML.attrib['type'])
delId = (category, id_type) delId = (category, id_type)
if idData == delId: if idData == delId:
self.xml.remove(idXML) self.xml.remove(idXML)
class DiscoItems(ElementBase): class DiscoItems(ElementBase):
namespace = 'http://jabber.org/protocol/disco#items' namespace = 'http://jabber.org/protocol/disco#items'
name = 'query' name = 'query'
plugin_attrib = 'disco_items' plugin_attrib = 'disco_items'
interfaces = set(('node', 'items')) interfaces = set(('node', 'items'))
def getItems(self): def getItems(self):
items = [] items = []
itemsXML = self.xml.findall('{%s}item' % self.namespace) itemsXML = self.xml.findall('{%s}item' % self.namespace)
for item in itemsXML: for item in itemsXML:
itemData = (item.attrib['jid'], itemData = (item.attrib['jid'],
item.attrib.get('node'), item.attrib.get('node'),
item.attrib.get('name')) item.attrib.get('name'))
items.append(itemData) items.append(itemData)
return items return items
def setItems(self, items): def setItems(self, items):
self.delItems() self.delItems()
for item in items: for item in items:
self.addItem(*item) self.addItem(*item)
def delItems(self): def delItems(self):
itemsXML = self.xml.findall('{%s}item' % self.namespace) itemsXML = self.xml.findall('{%s}item' % self.namespace)
for item in itemsXML: for item in itemsXML:
self.xml.remove(item) self.xml.remove(item)
def addItem(self, jid, node='', name=''): def addItem(self, jid, node='', name=''):
itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid})
if name: if name:
itemXML.attrib['name'] = name itemXML.attrib['name'] = name
if node: if node:
itemXML.attrib['node'] = node itemXML.attrib['node'] = node
self.xml.append(itemXML) self.xml.append(itemXML)
def delItem(self, jid, node=''):
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for itemXML in itemsXML:
itemData = (itemXML.attrib['jid'],
itemXML.attrib.get('node', ''))
itemDel = (jid, node)
if itemData == itemDel:
self.xml.remove(itemXML)
def delItem(self, jid, node=''):
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for itemXML in itemsXML:
itemData = (itemXML.attrib['jid'],
itemXML.attrib.get('node', ''))
itemDel = (jid, node)
if itemData == itemDel:
self.xml.remove(itemXML)
class DiscoNode(object): class DiscoNode(object):
""" """
Collection object for grouping info and item information Collection object for grouping info and item information
into nodes. into nodes.
""" """
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
self.info = DiscoInfo() self.info = DiscoInfo()
self.items = DiscoItems() self.items = DiscoItems()
self.info['node'] = name self.info['node'] = name
self.items['node'] = name self.items['node'] = name
# This is a bit like poor man's inheritance, but # This is a bit like poor man's inheritance, but
# to simplify adding information to the node we # to simplify adding information to the node we
# map node functions to either the info or items # map node functions to either the info or items
# stanza objects. # stanza objects.
# #
# We don't want to make DiscoNode inherit from # We don't want to make DiscoNode inherit from
# DiscoInfo and DiscoItems because DiscoNode is # DiscoInfo and DiscoItems because DiscoNode is
# not an actual stanza, and doing so would create # not an actual stanza, and doing so would create
# confusion and potential bugs. # confusion and potential bugs.
self._map(self.items, 'items', ['get', 'set', 'del']) self._map(self.items, 'items', ['get', 'set', 'del'])
self._map(self.items, 'item', ['add', 'del']) self._map(self.items, 'item', ['add', 'del'])
self._map(self.info, 'identities', ['get', 'set', 'del']) self._map(self.info, 'identities', ['get', 'set', 'del'])
self._map(self.info, 'identity', ['add', 'del']) self._map(self.info, 'identity', ['add', 'del'])
self._map(self.info, 'features', ['get', 'set', 'del']) self._map(self.info, 'features', ['get', 'set', 'del'])
self._map(self.info, 'feature', ['add', 'del']) self._map(self.info, 'feature', ['add', 'del'])
def isEmpty(self): def isEmpty(self):
""" """
Test if the node contains any information. Useful for Test if the node contains any information. Useful for
determining if a node can be deleted. determining if a node can be deleted.
""" """
ids = self.getIdentities() ids = self.getIdentities()
features = self.getFeatures() features = self.getFeatures()
items = self.getItems() items = self.getItems()
if not ids and not features and not items: if not ids and not features and not items:
return True return True
return False return False
def _map(self, obj, interface, access): def _map(self, obj, interface, access):
""" """
Map functions of the form obj.accessInterface Map functions of the form obj.accessInterface
to self.accessInterface for each given access type. to self.accessInterface for each given access type.
""" """
interface = interface.title() interface = interface.title()
for access_type in access: for access_type in access:
method = access_type + interface method = access_type + interface
if hasattr(obj, method): if hasattr(obj, method):
setattr(self, method, getattr(obj, method)) setattr(self, method, getattr(obj, method))
class xep_0030(base.base_plugin): class xep_0030(base.base_plugin):
""" """
XEP-0030 Service Discovery XEP-0030 Service Discovery
""" """
def plugin_init(self):
self.xep = '0030'
self.description = 'Service Discovery'
self.xmpp.registerHandler( def plugin_init(self):
Callback('Disco Items', self.xep = '0030'
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, self.description = 'Service Discovery'
DiscoItems.namespace)),
self.handle_item_query))
self.xmpp.registerHandler( self.xmpp.registerHandler(
Callback('Disco Info', Callback('Disco Items',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
DiscoInfo.namespace)), DiscoItems.namespace)),
self.handle_info_query)) self.handle_item_query))
registerStanzaPlugin(Iq, DiscoInfo) self.xmpp.registerHandler(
registerStanzaPlugin(Iq, DiscoItems) Callback('Disco Info',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
DiscoInfo.namespace)),
self.handle_info_query))
self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) registerStanzaPlugin(Iq, DiscoInfo)
self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) registerStanzaPlugin(Iq, DiscoItems)
self.nodes = {'main': DiscoNode('main')} self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items)
self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info)
def add_node(self, node): self.nodes = {'main': DiscoNode('main')}
if node not in self.nodes:
self.nodes[node] = DiscoNode(node)
def del_node(self, node): def add_node(self, node):
if node in self.nodes: if node not in self.nodes:
del self.nodes[node] self.nodes[node] = DiscoNode(node)
def handle_item_query(self, iq): def del_node(self, node):
if iq['type'] == 'get': if node in self.nodes:
logging.debug("Items requested by %s" % iq['from']) del self.nodes[node]
self.xmpp.event('disco_items_request', iq)
elif iq['type'] == 'result':
logging.debug("Items result from %s" % iq['from'])
self.xmpp.event('disco_items', iq)
def handle_info_query(self, iq): def handle_item_query(self, iq):
if iq['type'] == 'get': if iq['type'] == 'get':
logging.debug("Info requested by %s" % iq['from']) logging.debug("Items requested by %s" % iq['from'])
self.xmpp.event('disco_info_request', iq) self.xmpp.event('disco_items_request', iq)
elif iq['type'] == 'result': elif iq['type'] == 'result':
logging.debug("Info result from %s" % iq['from']) logging.debug("Items result from %s" % iq['from'])
self.xmpp.event('disco_info', iq) self.xmpp.event('disco_items', iq)
def handle_disco_info(self, iq, forwarded=False): def handle_info_query(self, iq):
""" if iq['type'] == 'get':
A default handler for disco#info requests. If another logging.debug("Info requested by %s" % iq['from'])
handler is registered, this one will defer and not run. self.xmpp.event('disco_info_request', iq)
""" elif iq['type'] == 'result':
handlers = self.xmpp.event_handlers['disco_info_request'] logging.debug("Info result from %s" % iq['from'])
if not forwarded and len(handlers) > 1: self.xmpp.event('disco_info', iq)
return
node_name = iq['disco_info']['node'] def handle_disco_info(self, iq, forwarded=False):
if not node_name: """
node_name = 'main' A default handler for disco#info requests. If another
handler is registered, this one will defer and not run.
"""
if not forwarded and self.xmpp.event_handled('disco_info_request'):
return
logging.debug("Using default handler for disco#info on node '%s'." % node_name) node_name = iq['disco_info']['node']
if not node_name:
node_name = 'main'
if node_name in self.nodes: logging.debug("Using default handler for disco#info on node '%s'." % node_name)
node = self.nodes[node_name]
iq.reply().setPayload(node.info.xml).send()
else:
logging.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_info'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
iq.send()
def handle_disco_items(self, iq, forwarded=False):
"""
A default handler for disco#items requests. If another
handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with if node_name in self.nodes:
forwarded set to True, then it will run as normal. node = self.nodes[node_name]
""" iq.reply().setPayload(node.info.xml).send()
handlers = self.xmpp.event_handlers['disco_items_request'] else:
if not forwarded and len(handlers) > 1: logging.debug("Node %s requested, but does not exist." % node_name)
return iq.reply().error().setPayload(iq['disco_info'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
iq.send()
node_name = iq['disco_items']['node'] def handle_disco_items(self, iq, forwarded=False):
if not node_name: """
node_name = 'main' A default handler for disco#items requests. If another
handler is registered, this one will defer and not run.
logging.debug("Using default handler for disco#items on node '%s'." % node_name) If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
if not forwarded and self.xmpp.event_handled('disco_items_request'):
return
if node_name in self.nodes: node_name = iq['disco_items']['node']
node = self.nodes[node_name] if not node_name:
iq.reply().setPayload(node.items.xml).send() node_name = 'main'
else:
logging.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_items'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
iq.send()
# Older interface methods for backwards compatibility logging.debug("Using default handler for disco#items on node '%s'." % node_name)
def getInfo(self, jid, node='', dfrom=None): if node_name in self.nodes:
iq = self.xmpp.Iq() node = self.nodes[node_name]
iq['type'] = 'get' iq.reply().setPayload(node.items.xml).send()
iq['to'] = jid else:
iq['from'] = dfrom logging.debug("Node %s requested, but does not exist." % node_name)
iq['disco_info']['node'] = node iq.reply().error().setPayload(iq['disco_items'].xml)
return iq.send() iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
iq.send()
def getItems(self, jid, node='', dfrom=None): # Older interface methods for backwards compatibility
iq = self.xmpp.Iq()
iq['type'] = 'get' def getInfo(self, jid, node='', dfrom=None):
iq['to'] = jid iq = self.xmpp.Iq()
iq['from'] = dfrom iq['type'] = 'get'
iq['disco_items']['node'] = node iq['to'] = jid
return iq.send() iq['from'] = dfrom
iq['disco_info']['node'] = node
def add_feature(self, feature, node='main'): return iq.send()
self.add_node(node)
self.nodes[node].addFeature(feature) def getItems(self, jid, node='', dfrom=None):
iq = self.xmpp.Iq()
def add_identity(self, category='', itype='', name='', node='main'): iq['type'] = 'get'
self.add_node(node) iq['to'] = jid
self.nodes[node].addIdentity(category=category, iq['from'] = dfrom
id_type=itype, iq['disco_items']['node'] = node
name=name) return iq.send()
def add_item(self, jid=None, name='', node='main', subnode=''): def add_feature(self, feature, node='main'):
self.add_node(node) self.add_node(node)
self.add_node(subnode) self.nodes[node].addFeature(feature)
if jid is None:
jid = self.xmpp.fulljid def add_identity(self, category='', itype='', name='', node='main'):
self.nodes[node].addItem(jid=jid, name=name, node=subnode) self.add_node(node)
self.nodes[node].addIdentity(category=category,
id_type=itype,
name=name)
def add_item(self, jid=None, name='', node='main', subnode=''):
self.add_node(node)
self.add_node(subnode)
if jid is None:
jid = self.xmpp.fulljid
self.nodes[node].addItem(jid=jid, name=name, node=subnode)

View file

@ -379,6 +379,7 @@ class XMLStream(object):
""" """
if self.ssl_support: if self.ssl_support:
logging.info("Negotiating TLS") logging.info("Negotiating TLS")
logging.info("Using SSL version: %s" % str(self.ssl_version))
ssl_socket = ssl.wrap_socket(self.socket, ssl_socket = ssl.wrap_socket(self.socket,
ssl_version=self.ssl_version, ssl_version=self.ssl_version,
do_handshake_on_connect=False) do_handshake_on_connect=False)
@ -527,6 +528,17 @@ class XMLStream(object):
self.__event_handlers[name] = filter(filter_pointers, self.__event_handlers[name] = filter(filter_pointers,
self.__event_handlers[name]) self.__event_handlers[name])
def event_handled(self, name):
"""
Indicates if an event has any associated handlers.
Returns the number of registered handlers.
Arguments:
name -- The name of the event to check.
"""
return len(self.__event_handlers.get(name, []))
def event(self, name, data={}, direct=False): def event(self, name, data={}, direct=False):
""" """
Manually trigger a custom event. Manually trigger a custom event.