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/caps.py125
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