diff options
Diffstat (limited to 'sleekxmpp/plugins/xep_0115')
-rw-r--r-- | sleekxmpp/plugins/xep_0115/caps.py | 125 |
1 files changed, 74 insertions, 51 deletions
diff --git a/sleekxmpp/plugins/xep_0115/caps.py b/sleekxmpp/plugins/xep_0115/caps.py index 8ce10edb..41b5c52e 100644 --- a/sleekxmpp/plugins/xep_0115/caps.py +++ b/sleekxmpp/plugins/xep_0115/caps.py @@ -9,8 +9,9 @@ import logging import hashlib import base64 +import threading -import sleekxmpp +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 @@ -33,19 +34,19 @@ class XEP_0115(BasePlugin): 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} - self.hash = self.config.get('hash', 'sha-1') - self.caps_node = self.config.get('caps_node', None) - self.broadcast = self.config.get('broadcast', True) - if self.caps_node is None: - ver = sleekxmpp.__version__ - self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver + self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__ register_stanza_plugin(Presence, stanza.Capabilities) register_stanza_plugin(StreamFeatures, stanza.Capabilities) @@ -89,6 +90,9 @@ class XEP_0115(BasePlugin): 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) @@ -103,12 +107,17 @@ class XEP_0115(BasePlugin): self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) def _filter_add_caps(self, stanza): - if isinstance(stanza, Presence) and self.broadcast: - ver = self.get_verstring(stanza['from']) - if ver: - stanza['caps']['node'] = self.caps_node - stanza['caps']['hash'] = self.hash - stanza['caps']['ver'] = ver + 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): @@ -129,12 +138,22 @@ class XEP_0115(BasePlugin): def _process_caps(self, pres): if not pres['caps']['hash']: - log.debug("Received unsupported legacy caps.") + 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(pres['caps']['ver']): + 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: @@ -145,9 +164,16 @@ class XEP_0115(BasePlugin): except XMPPError: return - log.debug("New caps verification string: %s", pres['caps']['ver']) + # 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'], pres['caps']['ver']) + node = '%s#%s' % (pres['caps']['node'], ver) caps = self.xmpp['xep_0030'].get_info(pres['from'], node) if isinstance(caps, Iq): @@ -157,7 +183,10 @@ class XEP_0115(BasePlugin): pres['caps']['ver']): self.assign_verstring(pres['from'], pres['caps']['ver']) except XMPPError: - log.debug("Could not retrieve disco#info results for caps") + 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 @@ -168,7 +197,6 @@ class XEP_0115(BasePlugin): return False # Check Features - full_features = caps.get_features(dedupe=False) deduped_features = caps.get_features() if len(full_features) != len(deduped_features): @@ -179,29 +207,32 @@ class XEP_0115(BasePlugin): form_types = [] deduped_form_types = set() for stanza in caps['substanzas']: - if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): - 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") + 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 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") + 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: @@ -261,7 +292,7 @@ class XEP_0115(BasePlugin): binary = hash(S.encode('utf8')).digest() return base64.b64encode(binary).decode('utf-8') - def update_caps(self, jid=None, node=None): + 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): @@ -275,19 +306,11 @@ class XEP_0115(BasePlugin): self.assign_verstring(jid, ver) if self.xmpp.session_started_event.is_set() and self.broadcast: - # Check if we've sent directed presence. If we haven't, we - # can just send a normal presence stanza. If we have, then - # we will send presence to each contact individually so - # that we don't clobber existing statuses. - directed = False - for contact in self.xmpp.roster[jid]: - if self.xmpp.roster[jid][contact].last_status is not None: - directed = True - if not directed: - self.xmpp.roster[jid].send_last_presence() - else: + 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 |