diff options
Diffstat (limited to 'sleekxmpp/plugins/xep_0115')
-rw-r--r-- | sleekxmpp/plugins/xep_0115/__init__.py | 20 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0115/caps.py | 345 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0115/stanza.py | 19 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0115/static.py | 146 |
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) |