Commit a4c7b013 authored by Philipp Hörist's avatar Philipp Hörist

Use nbxmpp's compute_caps_hash()

parent 1e1ced9a
Pipeline #3800 passed with stages
in 2 minutes and 44 seconds
......@@ -27,8 +27,6 @@ CapsCache caches features to hash relationships. The cache is queried
through ClientCaps objects which are hold by contact instances.
"""
import base64
import hashlib
import logging
import nbxmpp
......@@ -102,55 +100,6 @@ def create_suitable_client_caps(node, caps_hash, hash_method, fjid=None):
client_caps = ClientCaps(caps_hash, node, hash_method)
return client_caps
def compute_caps_hash(identities, features, dataforms=None, hash_method='sha-1'):
"""
Compute caps hash according to XEP-0115, V1.5
"""
if dataforms is None:
dataforms = []
def sort_identities_key(i):
return (i.category, i.type, i.lang or '')
def sort_dataforms_key(dataform):
return dataform['FORM_TYPE'].value
S = ''
identities.sort(key=sort_identities_key)
for i in identities:
c = i.category
type_ = i.type
lang = i.lang or ''
name = i.name or ''
S += '%s/%s/%s/%s<' % (c, type_, lang, name)
features.sort()
for f in features:
S += '%s<' % f
dataforms.sort(key=sort_dataforms_key)
for dataform in dataforms:
# fields indexed by var
fields = {}
for f in dataform.iter_fields():
values = f.getTags('value')
fields[f.var] = [value.getData() for value in values]
form_type = fields.get('FORM_TYPE')
if form_type:
S += form_type[0] + '<'
del fields['FORM_TYPE']
for var in sorted(fields.keys()):
S += '%s<' % var
values = sorted(fields[var])
for value in values:
S += '%s<' % value
if hash_method == 'sha-1':
hash_ = hashlib.sha1(S.encode('utf-8'))
elif hash_method == 'md5':
hash_ = hashlib.md5(S.encode('utf-8'))
else:
return ''
return base64.b64encode(hash_.digest()).decode('utf-8')
################################################################################
### Internal classes of this module
......@@ -184,15 +133,6 @@ class AbstractClientCaps:
"""
raise NotImplementedError
def get_hash_validation_strategy(self):
return self._is_hash_valid
def _is_hash_valid(self, identities, features, dataforms):
"""
To be implemented by subclasses
"""
raise NotImplementedError
class ClientCaps(AbstractClientCaps):
"""
......@@ -210,12 +150,6 @@ class ClientCaps(AbstractClientCaps):
connection.get_module('Discovery').disco_contact(
jid, '%s#%s' % (self._node, self._hash))
def _is_hash_valid(self, identities, features, dataforms):
computed_hash = compute_caps_hash(
identities, features, dataforms=dataforms,
hash_method=self._hash_method)
return computed_hash == self._hash
class OldClientCaps(AbstractClientCaps):
"""
......@@ -231,8 +165,6 @@ class OldClientCaps(AbstractClientCaps):
def _discover(self, connection, jid):
connection.get_module('Discovery').disco_contact(jid)
def _is_hash_valid(self, identities, features, dataforms):
return True
class NoClientCaps(AbstractClientCaps):
"""
......@@ -248,8 +180,6 @@ class NoClientCaps(AbstractClientCaps):
def _discover(self, connection, jid):
connection.get_module('Discovery').disco_contact(jid)
def _is_hash_valid(self, identities, features, dataforms):
return True
class NullClientCaps(AbstractClientCaps):
"""
......@@ -282,9 +212,6 @@ class NullClientCaps(AbstractClientCaps):
def _discover(self, connection, jid):
pass
def _is_hash_valid(self, identities, features, dataforms):
return False
class CapsCache:
"""
......
......@@ -56,7 +56,9 @@ from encodings.punycode import punycode_encode
from functools import wraps
import nbxmpp
from nbxmpp.util import compute_caps_hash
from nbxmpp.stringprepare import nameprep
from nbxmpp.structs import DiscoInfo
from gi.repository import GLib
import precis_i18n.codec # pylint: disable=unused-import
......@@ -1117,9 +1119,10 @@ def update_optional_features(account=None):
# Give plugins the possibility to add their features
app.plugin_manager.extension_point('update_caps', account_)
app.caps_hash[account_] = caps_cache.compute_caps_hash(
[app.gajim_identity],
app.gajim_common_features + features)
app.caps_hash[account_] = compute_caps_hash(DiscoInfo(
None, None, [app.gajim_identity],
app.gajim_common_features + features,
[]), compare=False)
# re-send presence with new hash
connected = app.connections[account_].connected
if connected > 1 and app.SHOW_LIST[connected] != 'invisible':
......
......@@ -20,6 +20,7 @@
import nbxmpp
from nbxmpp.structs import StanzaHandler
from nbxmpp.util import is_error_result
from nbxmpp.util import compute_caps_hash
from gajim.common import caps_cache
from gajim.common import app
......@@ -120,18 +121,18 @@ class Caps(BaseModule):
# the identities and features
return
validate = contact.client_caps.get_hash_validation_strategy()
hash_is_valid = validate(info.identities, info.features, info.dataforms)
if hash_is_valid:
cache_item.set_and_store(info.identities, info.features)
else:
try:
compute_caps_hash(info)
except Exception as error:
self._log.warning('Disco info malformed: %s %s',
contact.get_full_jid(), error)
self._log.warning(info)
node = caps_hash = hash_method = None
contact.client_caps = self._create_suitable_client_caps(
node, caps_hash, hash_method)
self._log.warning(
'Computed and retrieved caps hash differ. Ignoring '
'caps of contact %s', contact.get_full_jid())
else:
cache_item.set_and_store(info.identities, info.features)
app.nec.push_incoming_event(
NetworkEvent('caps-update',
conn=self._con,
......
......@@ -2,134 +2,12 @@
Tests for capabilities and the capabilities cache
'''
import unittest
import weakref
from unittest.mock import MagicMock, Mock
from nbxmpp import NS_MUC, NS_PING, NS_XHTML_IM, Iq
from nbxmpp import NS_MUC, NS_PING, NS_XHTML_IM
from nbxmpp.structs import DiscoIdentity
from nbxmpp.modules.discovery import Discovery
from gajim.common import caps_cache as caps
EXAMPLES = [
'''<iq type='result'>
<query xmlns='http://jabber.org/protocol/disco#info' node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='>
<identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/>
<identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/>
<feature var='http://jabber.org/protocol/caps'/>
<feature var='http://jabber.org/protocol/disco#info'/>
<feature var='http://jabber.org/protocol/disco#items'/>
<feature var='http://jabber.org/protocol/muc'/>
<x xmlns='jabber:x:data' type='result'>
<field var='FORM_TYPE' type='hidden'>
<value>urn:xmpp:dataforms:softwareinfo</value>
</field>
<field var='ip_version'>
<value>ipv4</value>
<value>ipv6</value>
</field>
<field var='os'>
<value>Mac</value>
</field>
<field var='os_version'>
<value>10.5.1</value>
</field>
<field var='software'>
<value>Psi</value>
</field>
<field var='software_version'>
<value>0.11</value>
</field>
</x>
</query>
</iq>
''',
'''<iq type='result'>
<query node="http://bombusmod.net.ru/caps#tbBQGBMv8g8U7kW55TEZZRnMCJ4=" xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" name="BombusMod" type="mobile"/>
<feature var="http://jabber.org/protocol/chatstates"/>
<feature var="http://jabber.org/protocol/disco#info"/>
<feature var="http://jabber.org/protocol/disco#items"/>
<feature var="http://jabber.org/protocol/rosterx"/>
<feature var="jabber:iq:last"/>
<feature var="jabber:iq:privacy"/>
<feature var="jabber:iq:roster"/>
<feature var="jabber:iq:time"/>
<feature var="jabber:iq:version"/>
<feature var="jabber:x:oob"/>
<feature var="urn:xmpp:ping"/>
<feature var="urn:xmpp:receipts"/>
<feature var="urn:xmpp:time"/>
</query>
</iq>
''',
'''<iq type='result'>
<query node="http://jappix.org/#qRsaGbKTz8EwAOakYO00InkZUxM=" xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" name="Jappix" type="web"/>
<feature var="http://jabber.org/protocol/activity"/>
<feature var="http://jabber.org/protocol/activity+notify"/>
<feature var="http://jabber.org/protocol/caps"/>
<feature var="http://jabber.org/protocol/chatstates"/>
<feature var="http://jabber.org/protocol/commands"/>
<feature var="http://jabber.org/protocol/disco#info"/>
<feature var="http://jabber.org/protocol/disco#items"/>
<feature var="http://jabber.org/protocol/geoloc"/>
<feature var="http://jabber.org/protocol/geoloc+notify"/>
<feature var="http://jabber.org/protocol/http-auth"/>
<feature var="http://jabber.org/protocol/httpbind"/>
<feature var="http://jabber.org/protocol/mood"/>
<feature var="http://jabber.org/protocol/mood+notify"/>
<feature var="http://jabber.org/protocol/muc"/>
<feature var="http://jabber.org/protocol/muc#admin"/>
<feature var="http://jabber.org/protocol/muc#owner"/>
<feature var="http://jabber.org/protocol/muc#roomconfig"/>
<feature var="http://jabber.org/protocol/muc#user"/>
<feature var="http://jabber.org/protocol/nick"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-items"/>
<feature var="http://jabber.org/protocol/rosterx"/>
<feature var="http://jabber.org/protocol/tune"/>
<feature var="http://jabber.org/protocol/tune+notify"/>
<feature var="http://jabber.org/protocol/xhtml-im"/>
<feature var="ipv6"/>
<feature var="jabber:iq:last"/>
<feature var="jabber:iq:oob"/>
<feature var="jabber:iq:privacy"/>
<feature var="jabber:iq:private"/>
<feature var="jabber:iq:register"/>
<feature var="jabber:iq:roster"/>
<feature var="jabber:iq:search"/>
<feature var="jabber:iq:version"/>
<feature var="jabber:x:data"/>
<feature var="jabber:x:oob"/>
<feature var="urn:ietf:params:xml:ns:vcard-4.0"/>
<feature var="urn:ietf:rfc:3264"/>
<feature var="urn:xmpp:avatar:data"/>
<feature var="urn:xmpp:avatar:metadata"/>
<feature var="urn:xmpp:delay"/>
<feature var="urn:xmpp:extdisco:1"/>
<feature var="urn:xmpp:inbox"/>
<feature var="urn:xmpp:inbox+notify"/>
<feature var="urn:xmpp:jingle:1"/>
<feature var="urn:xmpp:jingle:apps:rtp:1"/>
<feature var="urn:xmpp:jingle:apps:rtp:audio"/>
<feature var="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"/>
<feature var="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"/>
<feature var="urn:xmpp:jingle:apps:rtp:video"/>
<feature var="urn:xmpp:jingle:apps:rtp:zrtp:1"/>
<feature var="urn:xmpp:jingle:transports:ice-udp:1"/>
<feature var="urn:xmpp:mam:0"/>
<feature var="urn:xmpp:microblog:0"/>
<feature var="urn:xmpp:microblog:0+notify"/>
<feature var="urn:xmpp:ping"/>
<feature var="urn:xmpp:receipts"/>
<feature var="urn:xmpp:time"/>
<feature var="urn:xmpp:tmp:jingle:apps:dtls:0"/>
<feature var="vcard-temp"/>
</query>
</iq>''',
]
class CommonCapsTest(unittest.TestCase):
......@@ -214,19 +92,6 @@ class TestCapsCache(CommonCapsTest):
self.assertEqual(0, connection.get_module('Discovery').disco_contact.call_count)
def test_hash(self):
'''tests the hash computation'''
for example in EXAMPLES:
stanza = Iq(node=example)
disco_module = Discovery(None)
weak_callback = weakref.WeakMethod(self.disco_info_received)
disco_module._disco_info_received(None, stanza, callback=weak_callback)
def disco_info_received(self, info):
computed_hash = caps.compute_caps_hash(info.identities, info.features, info.dataforms)
hash_ = info.node.split('#')[1]
self.assertEqual(hash_, computed_hash)
class TestClientCaps(CommonCapsTest):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment