summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins/xep_0115
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/plugins/xep_0115')
-rw-r--r--sleekxmpp/plugins/xep_0115/__init__.py20
-rw-r--r--sleekxmpp/plugins/xep_0115/caps.py345
-rw-r--r--sleekxmpp/plugins/xep_0115/stanza.py19
-rw-r--r--sleekxmpp/plugins/xep_0115/static.py146
4 files changed, 0 insertions, 530 deletions
diff --git a/sleekxmpp/plugins/xep_0115/__init__.py b/sleekxmpp/plugins/xep_0115/__init__.py
deleted file mode 100644
index 31a2c03a..00000000
--- a/sleekxmpp/plugins/xep_0115/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0115.stanza import Capabilities
-from sleekxmpp.plugins.xep_0115.static import StaticCaps
-from sleekxmpp.plugins.xep_0115.caps import XEP_0115
-
-
-register_plugin(XEP_0115)
-
-
-# Retain some backwards compatibility
-xep_0115 = XEP_0115
diff --git a/sleekxmpp/plugins/xep_0115/caps.py b/sleekxmpp/plugins/xep_0115/caps.py
deleted file mode 100644
index 41b5c52e..00000000
--- a/sleekxmpp/plugins/xep_0115/caps.py
+++ /dev/null
@@ -1,345 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import hashlib
-import base64
-import threading
-
-from sleekxmpp import __version__
-from sleekxmpp.stanza import StreamFeatures, Presence, Iq
-from sleekxmpp.xmlstream import register_stanza_plugin, JID
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0115 import stanza, StaticCaps
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0115(BasePlugin):
-
- """
- XEP-0115: Entity Capabalities
- """
-
- name = 'xep_0115'
- description = 'XEP-0115: Entity Capabilities'
- dependencies = set(['xep_0030', 'xep_0128', 'xep_0004'])
- stanza = stanza
- default_config = {
- 'hash': 'sha-1',
- 'caps_node': None,
- 'broadcast': True
- }
-
- def plugin_init(self):
- self.hashes = {'sha-1': hashlib.sha1,
- 'sha1': hashlib.sha1,
- 'md5': hashlib.md5}
-
- if self.caps_node is None:
- self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__
-
- register_stanza_plugin(Presence, stanza.Capabilities)
- register_stanza_plugin(StreamFeatures, stanza.Capabilities)
-
- self._disco_ops = ['cache_caps',
- 'get_caps',
- 'assign_verstring',
- 'get_verstring',
- 'supports',
- 'has_identity']
-
- self.xmpp.register_handler(
- Callback('Entity Capabilites',
- StanzaPath('presence/caps'),
- self._handle_caps))
-
- self.xmpp.add_filter('out', self._filter_add_caps)
-
- self.xmpp.add_event_handler('entity_caps', self._process_caps,
- threaded=True)
-
- if not self.xmpp.is_component:
- self.xmpp.register_feature('caps',
- self._handle_caps_feature,
- restart=False,
- order=10010)
-
- disco = self.xmpp['xep_0030']
- self.static = StaticCaps(self.xmpp, disco.static)
-
- for op in self._disco_ops:
- self.api.register(getattr(self.static, op), op, default=True)
-
- for op in ('supports', 'has_identity'):
- self.xmpp['xep_0030'].api.register(getattr(self.static, op), op)
-
- self._run_node_handler = disco._run_node_handler
-
- disco.cache_caps = self.cache_caps
- disco.update_caps = self.update_caps
- disco.assign_verstring = self.assign_verstring
- disco.get_verstring = self.get_verstring
-
- self._processing_lock = threading.Lock()
- self._processing = set()
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
- self.xmpp.del_filter('out', self._filter_add_caps)
- self.xmpp.del_event_handler('entity_caps', self._process_caps)
- self.xmpp.remove_handler('Entity Capabilities')
- if not self.xmpp.is_component:
- self.xmpp.unregister_feature('caps', 10010)
- for op in ('supports', 'has_identity'):
- self.xmpp['xep_0030'].restore_defaults(op)
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace)
-
- def _filter_add_caps(self, stanza):
- if not isinstance(stanza, Presence) or not self.broadcast:
- return stanza
-
- if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'):
- return stanza
-
- ver = self.get_verstring(stanza['from'])
- if ver:
- stanza['caps']['node'] = self.caps_node
- stanza['caps']['hash'] = self.hash
- stanza['caps']['ver'] = ver
- return stanza
-
- def _handle_caps(self, presence):
- if not self.xmpp.is_component:
- if presence['from'] == self.xmpp.boundjid:
- return
- self.xmpp.event('entity_caps', presence)
-
- def _handle_caps_feature(self, features):
- # We already have a method to process presence with
- # caps, so wrap things up and use that.
- p = Presence()
- p['from'] = self.xmpp.boundjid.domain
- p.append(features['caps'])
- self.xmpp.features.add('caps')
-
- self.xmpp.event('entity_caps', p)
-
- def _process_caps(self, pres):
- if not pres['caps']['hash']:
- log.debug("Received unsupported legacy caps: %s, %s, %s",
- pres['caps']['node'],
- pres['caps']['ver'],
- pres['caps']['ext'])
- self.xmpp.event('entity_caps_legacy', pres)
- return
-
- ver = pres['caps']['ver']
-
- existing_verstring = self.get_verstring(pres['from'].full)
- if str(existing_verstring) == str(ver):
- return
-
- existing_caps = self.get_caps(verstring=ver)
- if existing_caps is not None:
- self.assign_verstring(pres['from'], ver)
- return
-
- if pres['caps']['hash'] not in self.hashes:
- try:
- log.debug("Unknown caps hash: %s", pres['caps']['hash'])
- self.xmpp['xep_0030'].get_info(jid=pres['from'])
- return
- except XMPPError:
- return
-
- # Only lookup the same caps once at a time.
- with self._processing_lock:
- if ver in self._processing:
- log.debug('Already processing verstring %s' % ver)
- return
- self._processing.add(ver)
-
- log.debug("New caps verification string: %s", ver)
- try:
- node = '%s#%s' % (pres['caps']['node'], ver)
- caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
-
- if isinstance(caps, Iq):
- caps = caps['disco_info']
-
- if self._validate_caps(caps, pres['caps']['hash'],
- pres['caps']['ver']):
- self.assign_verstring(pres['from'], pres['caps']['ver'])
- except XMPPError:
- log.debug("Could not retrieve disco#info results for caps for %s", node)
-
- with self._processing_lock:
- self._processing.remove(ver)
-
- def _validate_caps(self, caps, hash, check_verstring):
- # Check Identities
- full_ids = caps.get_identities(dedupe=False)
- deduped_ids = caps.get_identities()
- if len(full_ids) != len(deduped_ids):
- log.debug("Duplicate disco identities found, invalid for caps")
- return False
-
- # Check Features
- full_features = caps.get_features(dedupe=False)
- deduped_features = caps.get_features()
- if len(full_features) != len(deduped_features):
- log.debug("Duplicate disco features found, invalid for caps")
- return False
-
- # Check Forms
- form_types = []
- deduped_form_types = set()
- for stanza in caps['substanzas']:
- if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
- log.debug("Non form extension found, ignoring for caps")
- caps.xml.remove(stanza.xml)
- continue
- if 'FORM_TYPE' in stanza['fields']:
- f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
- form_types.append(f_type)
- deduped_form_types.add(f_type)
- if len(form_types) != len(deduped_form_types):
- log.debug("Duplicated FORM_TYPE values, " + \
- "invalid for caps")
- return False
-
- if len(f_type) > 1:
- deduped_type = set(f_type)
- if len(f_type) != len(deduped_type):
- log.debug("Extra FORM_TYPE data, invalid for caps")
- return False
-
- if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
- log.debug("Field FORM_TYPE type not 'hidden', " + \
- "ignoring form for caps")
- caps.xml.remove(stanza.xml)
- else:
- log.debug("No FORM_TYPE found, ignoring form for caps")
- caps.xml.remove(stanza.xml)
-
- verstring = self.generate_verstring(caps, hash)
- if verstring != check_verstring:
- log.debug("Verification strings do not match: %s, %s" % (
- verstring, check_verstring))
- return False
-
- self.cache_caps(verstring, caps)
- return True
-
- def generate_verstring(self, info, hash):
- hash = self.hashes.get(hash, None)
- if hash is None:
- return None
-
- S = ''
-
- # Convert None to '' in the identities
- def clean_identity(id):
- return map(lambda i: i or '', id)
- identities = map(clean_identity, info['identities'])
-
- identities = sorted(('/'.join(i) for i in identities))
- features = sorted(info['features'])
-
- S += '<'.join(identities) + '<'
- S += '<'.join(features) + '<'
-
- form_types = {}
-
- for stanza in info['substanzas']:
- if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
- if 'FORM_TYPE' in stanza['fields']:
- f_type = stanza['values']['FORM_TYPE']
- if len(f_type):
- f_type = f_type[0]
- if f_type not in form_types:
- form_types[f_type] = []
- form_types[f_type].append(stanza)
-
- sorted_forms = sorted(form_types.keys())
- for f_type in sorted_forms:
- for form in form_types[f_type]:
- S += '%s<' % f_type
- fields = sorted(form['fields'].keys())
- fields.remove('FORM_TYPE')
- for field in fields:
- S += '%s<' % field
- vals = form['fields'][field].get_value(convert=False)
- if vals is None:
- S += '<'
- else:
- if not isinstance(vals, list):
- vals = [vals]
- S += '<'.join(sorted(vals)) + '<'
-
- binary = hash(S.encode('utf8')).digest()
- return base64.b64encode(binary).decode('utf-8')
-
- def update_caps(self, jid=None, node=None, preserve=False):
- try:
- info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
- if isinstance(info, Iq):
- info = info['disco_info']
- ver = self.generate_verstring(info, self.hash)
- self.xmpp['xep_0030'].set_info(
- jid=jid,
- node='%s#%s' % (self.caps_node, ver),
- info=info)
- self.cache_caps(ver, info)
- self.assign_verstring(jid, ver)
-
- if self.xmpp.session_started_event.is_set() and self.broadcast:
- if self.xmpp.is_component or preserve:
- for contact in self.xmpp.roster[jid]:
- self.xmpp.roster[jid][contact].send_last_presence()
- else:
- self.xmpp.roster[jid].send_last_presence()
- except XMPPError:
- return
-
- def get_verstring(self, jid=None):
- if jid in ('', None):
- jid = self.xmpp.boundjid.full
- if isinstance(jid, JID):
- jid = jid.full
- return self.api['get_verstring'](jid)
-
- def assign_verstring(self, jid=None, verstring=None):
- if jid in (None, ''):
- jid = self.xmpp.boundjid.full
- if isinstance(jid, JID):
- jid = jid.full
- return self.api['assign_verstring'](jid, args={
- 'verstring': verstring})
-
- def cache_caps(self, verstring=None, info=None):
- data = {'verstring': verstring, 'info': info}
- return self.api['cache_caps'](args=data)
-
- def get_caps(self, jid=None, verstring=None):
- if verstring is None:
- if jid is not None:
- verstring = self.get_verstring(jid)
- else:
- return None
- if isinstance(jid, JID):
- jid = jid.full
- data = {'verstring': verstring}
- return self.api['get_caps'](jid, args=data)
diff --git a/sleekxmpp/plugins/xep_0115/stanza.py b/sleekxmpp/plugins/xep_0115/stanza.py
deleted file mode 100644
index 3e80b5cf..00000000
--- a/sleekxmpp/plugins/xep_0115/stanza.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from __future__ import unicode_literals
-
-from sleekxmpp.xmlstream import ElementBase
-
-
-class Capabilities(ElementBase):
-
- namespace = 'http://jabber.org/protocol/caps'
- name = 'c'
- plugin_attrib = 'caps'
- interfaces = set(('hash', 'node', 'ver', 'ext'))
diff --git a/sleekxmpp/plugins/xep_0115/static.py b/sleekxmpp/plugins/xep_0115/static.py
deleted file mode 100644
index f83c244c..00000000
--- a/sleekxmpp/plugins/xep_0115/static.py
+++ /dev/null
@@ -1,146 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.exceptions import IqError, IqTimeout
-
-
-log = logging.getLogger(__name__)
-
-
-class StaticCaps(object):
-
- """
- Extend the default StaticDisco implementation to provide
- support for extended identity information.
- """
-
- def __init__(self, xmpp, static):
- """
- Augment the default XEP-0030 static handler object.
-
- Arguments:
- static -- The default static XEP-0030 handler object.
- """
- self.xmpp = xmpp
- self.disco = self.xmpp['xep_0030']
- self.caps = self.xmpp['xep_0115']
- self.static = static
- self.ver_cache = {}
- self.jid_vers = {}
-
- def supports(self, jid, node, ifrom, data):
- """
- Check if a JID supports a given feature.
-
- The data parameter may provide:
- feature -- The feature to check for support.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- """
- feature = data.get('feature', None)
-
- data = {'local': data.get('local', False),
- 'cached': data.get('cached', True)}
-
- if not feature:
- return False
-
- if node in (None, ''):
- info = self.caps.get_caps(jid)
- if info and feature in info['features']:
- return True
-
- try:
- info = self.disco.get_info(jid=jid, node=node,
- ifrom=ifrom, **data)
- info = self.disco._wrap(ifrom, jid, info, True)
- return feature in info['disco_info']['features']
- except IqError:
- return False
- except IqTimeout:
- return None
-
- def has_identity(self, jid, node, ifrom, data):
- """
- Check if a JID has a given identity.
-
- The data parameter may provide:
- category -- The category of the identity to check.
- itype -- The type of the identity to check.
- lang -- The language of the identity to check.
- local -- If true, then the query is for a JID/node
- combination handled by this Sleek instance and
- no stanzas need to be sent.
- Otherwise, a disco stanza must be sent to the
- remove JID to retrieve the info.
- cached -- If true, then look for the disco info data from
- the local cache system. If no results are found,
- send the query as usual. The self.use_cache
- setting must be set to true for this option to
- be useful. If set to false, then the cache will
- be skipped, even if a result has already been
- cached. Defaults to false.
- """
- identity = (data.get('category', None),
- data.get('itype', None),
- data.get('lang', None))
-
- data = {'local': data.get('local', False),
- 'cached': data.get('cached', True)}
-
- trunc = lambda i: (i[0], i[1], i[2])
-
- if node in (None, ''):
- info = self.caps.get_caps(jid)
- if info and identity in map(trunc, info['identities']):
- return True
-
- try:
- info = self.disco.get_info(jid=jid, node=node,
- ifrom=ifrom, **data)
- info = self.disco._wrap(ifrom, jid, info, True)
- return identity in map(trunc, info['disco_info']['identities'])
- except IqError:
- return False
- except IqTimeout:
- return None
-
- def cache_caps(self, jid, node, ifrom, data):
- with self.static.lock:
- verstring = data.get('verstring', None)
- info = data.get('info', None)
- if not verstring or not info:
- return
- self.ver_cache[verstring] = info
-
- def assign_verstring(self, jid, node, ifrom, data):
- with self.static.lock:
- if isinstance(jid, JID):
- jid = jid.full
- self.jid_vers[jid] = data.get('verstring', None)
-
- def get_verstring(self, jid, node, ifrom, data):
- with self.static.lock:
- return self.jid_vers.get(jid, None)
-
- def get_caps(self, jid, node, ifrom, data):
- with self.static.lock:
- return self.ver_cache.get(data.get('verstring', None), None)