Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop

This commit is contained in:
Lance Stout 2010-11-03 12:39:44 -04:00
commit 5769935720
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=''): def delItem(self, jid, node=''):
itemsXML = self.xml.findall('{%s}item' % self.namespace) itemsXML = self.xml.findall('{%s}item' % self.namespace)
for itemXML in itemsXML: for itemXML in itemsXML:
itemData = (itemXML.attrib['jid'], itemData = (itemXML.attrib['jid'],
itemXML.attrib.get('node', '')) itemXML.attrib.get('node', ''))
itemDel = (jid, node) itemDel = (jid, node)
if itemData == itemDel: if itemData == itemDel:
self.xml.remove(itemXML) 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): def plugin_init(self):
self.xep = '0030' self.xep = '0030'
self.description = 'Service Discovery' self.description = 'Service Discovery'
self.xmpp.registerHandler( self.xmpp.registerHandler(
Callback('Disco Items', Callback('Disco Items',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
DiscoItems.namespace)), DiscoItems.namespace)),
self.handle_item_query)) self.handle_item_query))
self.xmpp.registerHandler( self.xmpp.registerHandler(
Callback('Disco Info', Callback('Disco Info',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
DiscoInfo.namespace)), DiscoInfo.namespace)),
self.handle_info_query)) self.handle_info_query))
registerStanzaPlugin(Iq, DiscoInfo) registerStanzaPlugin(Iq, DiscoInfo)
registerStanzaPlugin(Iq, DiscoItems) registerStanzaPlugin(Iq, DiscoItems)
self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items)
self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info)
self.nodes = {'main': DiscoNode('main')} self.nodes = {'main': DiscoNode('main')}
def add_node(self, node): def add_node(self, node):
if node not in self.nodes: if node not in self.nodes:
self.nodes[node] = DiscoNode(node) self.nodes[node] = DiscoNode(node)
def del_node(self, node): def del_node(self, node):
if node in self.nodes: if node in self.nodes:
del self.nodes[node] del self.nodes[node]
def handle_item_query(self, iq): def handle_item_query(self, iq):
if iq['type'] == 'get': if iq['type'] == 'get':
logging.debug("Items requested by %s" % iq['from']) logging.debug("Items requested by %s" % iq['from'])
self.xmpp.event('disco_items_request', iq) self.xmpp.event('disco_items_request', iq)
elif iq['type'] == 'result': elif iq['type'] == 'result':
logging.debug("Items result from %s" % iq['from']) logging.debug("Items result from %s" % iq['from'])
self.xmpp.event('disco_items', iq) self.xmpp.event('disco_items', iq)
def handle_info_query(self, iq): def handle_info_query(self, iq):
if iq['type'] == 'get': if iq['type'] == 'get':
logging.debug("Info requested by %s" % iq['from']) logging.debug("Info requested by %s" % iq['from'])
self.xmpp.event('disco_info_request', iq) self.xmpp.event('disco_info_request', iq)
elif iq['type'] == 'result': elif iq['type'] == 'result':
logging.debug("Info result from %s" % iq['from']) logging.debug("Info result from %s" % iq['from'])
self.xmpp.event('disco_info', iq) self.xmpp.event('disco_info', iq)
def handle_disco_info(self, iq, forwarded=False): def handle_disco_info(self, iq, forwarded=False):
""" """
A default handler for disco#info requests. If another A default handler for disco#info requests. If another
handler is registered, this one will defer and not run. handler is registered, this one will defer and not run.
""" """
handlers = self.xmpp.event_handlers['disco_info_request'] if not forwarded and self.xmpp.event_handled('disco_info_request'):
if not forwarded and len(handlers) > 1: return
return
node_name = iq['disco_info']['node'] node_name = iq['disco_info']['node']
if not node_name: if not node_name:
node_name = 'main' node_name = 'main'
logging.debug("Using default handler for disco#info on node '%s'." % node_name) logging.debug("Using default handler for disco#info on node '%s'." % node_name)
if node_name in self.nodes: if node_name in self.nodes:
node = self.nodes[node_name] node = self.nodes[node_name]
iq.reply().setPayload(node.info.xml).send() iq.reply().setPayload(node.info.xml).send()
else: else:
logging.debug("Node %s requested, but does not exist." % node_name) logging.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_info'].xml) iq.reply().error().setPayload(iq['disco_info'].xml)
iq['error']['code'] = '404' iq['error']['code'] = '404'
iq['error']['type'] = 'cancel' iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found' iq['error']['condition'] = 'item-not-found'
iq.send() iq.send()
def handle_disco_items(self, iq, forwarded=False): def handle_disco_items(self, iq, forwarded=False):
""" """
A default handler for disco#items requests. If another A default handler for disco#items requests. If another
handler is registered, this one will defer and not run. handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal. forwarded set to True, then it will run as normal.
""" """
handlers = self.xmpp.event_handlers['disco_items_request'] if not forwarded and self.xmpp.event_handled('disco_items_request'):
if not forwarded and len(handlers) > 1: return
return
node_name = iq['disco_items']['node'] node_name = iq['disco_items']['node']
if not node_name: if not node_name:
node_name = 'main' node_name = 'main'
logging.debug("Using default handler for disco#items on node '%s'." % node_name) logging.debug("Using default handler for disco#items on node '%s'." % node_name)
if node_name in self.nodes: if node_name in self.nodes:
node = self.nodes[node_name] node = self.nodes[node_name]
iq.reply().setPayload(node.items.xml).send() iq.reply().setPayload(node.items.xml).send()
else: else:
logging.debug("Node %s requested, but does not exist." % node_name) logging.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_items'].xml) iq.reply().error().setPayload(iq['disco_items'].xml)
iq['error']['code'] = '404' iq['error']['code'] = '404'
iq['error']['type'] = 'cancel' iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found' iq['error']['condition'] = 'item-not-found'
iq.send() iq.send()
# Older interface methods for backwards compatibility # Older interface methods for backwards compatibility
def getInfo(self, jid, node='', dfrom=None): def getInfo(self, jid, node='', dfrom=None):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'get' iq['type'] = 'get'
iq['to'] = jid iq['to'] = jid
iq['from'] = dfrom iq['from'] = dfrom
iq['disco_info']['node'] = node iq['disco_info']['node'] = node
return iq.send() return iq.send()
def getItems(self, jid, node='', dfrom=None): def getItems(self, jid, node='', dfrom=None):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'get' iq['type'] = 'get'
iq['to'] = jid iq['to'] = jid
iq['from'] = dfrom iq['from'] = dfrom
iq['disco_items']['node'] = node iq['disco_items']['node'] = node
return iq.send() return iq.send()
def add_feature(self, feature, node='main'): def add_feature(self, feature, node='main'):
self.add_node(node) self.add_node(node)
self.nodes[node].addFeature(feature) self.nodes[node].addFeature(feature)
def add_identity(self, category='', itype='', name='', node='main'): def add_identity(self, category='', itype='', name='', node='main'):
self.add_node(node) self.add_node(node)
self.nodes[node].addIdentity(category=category, self.nodes[node].addIdentity(category=category,
id_type=itype, id_type=itype,
name=name) name=name)
def add_item(self, jid=None, name='', node='main', subnode=''): def add_item(self, jid=None, name='', node='main', subnode=''):
self.add_node(node) self.add_node(node)
self.add_node(subnode) self.add_node(subnode)
if jid is None: if jid is None:
jid = self.xmpp.fulljid jid = self.xmpp.fulljid
self.nodes[node].addItem(jid=jid, name=name, node=subnode) 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.