From 3295b08b260616bb088aee0cc7edbfe19e71e64b Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Mon, 26 Oct 2009 19:20:16 +0100 Subject: [PATCH] Two small caps enhancements. * Rename EntityCapabilities to ClientCaps as this seems more intense giving. * Add ability to blacklist features where we cannot savely assume that a client, which did not advertise caps, supports them --- src/common/caps.py | 38 ++++++++++++++++++------------ test/test_caps.py | 55 +++++++++++++++++++++++-------------------- test/test_contacts.py | 8 ++----- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/common/caps.py b/src/common/caps.py index 9ef228e33..ec0a9be51 100644 --- a/src/common/caps.py +++ b/src/common/caps.py @@ -28,9 +28,9 @@ Module containing all XEP-115 (Entity Capabilities) related classes Basic Idea: CapsCache caches features to hash relationships. The cache is queried -through EntityCapabilities objects which are hold by contact instances. +through ClientCaps objects which are hold by contact instances. -EntityCapabilities represent the client of contacts. It is set on the receive +ClientCaps represent the client of contacts. It is set on the receive of a presence. The respective jid is then queried with a disco if the advertised client/hash is unknown. ''' @@ -38,8 +38,13 @@ client/hash is unknown. import gajim import helpers +from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES -class AbstractEntityCapabilities(object): +# Features where we cannot safely assume that the other side supports them +FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION] + + +class AbstractClientCaps(object): ''' Base class representing a client and its capabilities as advertised by a caps tag in a presence @@ -62,12 +67,15 @@ class AbstractEntityCapabilities(object): self._discover(connection, jid) q.queried = 1 - def contains_feature(self, feature): + def contains_feature(self, requested_feature): ''' Returns true if these capabilities contain the given feature ''' - features = self._lookup_in_cache().features - - if feature in features or features == []: + cach_entry = self._lookup_in_cache() + supported_features = cach_entry.features + if requested_feature in supported_features: return True + elif supported_features == [] and cach_entry.queried in (0, 1): + # assume feature is supported, if not blacklisted + return requested_feature not in FEATURE_BLACKLIST else: return False @@ -80,11 +88,11 @@ class AbstractEntityCapabilities(object): raise NotImplementedError() -class EntityCapabilities(AbstractEntityCapabilities): +class ClientCaps(AbstractClientCaps): ''' The current XEP-115 implementation ''' def __init__(self, caps_cache, caps_hash, node, hash_method): - AbstractEntityCapabilities.__init__(self, caps_cache, caps_hash, node) + AbstractClientCaps.__init__(self, caps_cache, caps_hash, node) assert hash_method != 'old' self._hash_method = hash_method @@ -95,11 +103,11 @@ class EntityCapabilities(AbstractEntityCapabilities): connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) -class OldEntityCapabilities(AbstractEntityCapabilities): +class OldClientCaps(AbstractClientCaps): ''' Old XEP-115 implemtation. Kept around for background competability. ''' def __init__(self, caps_cache, caps_hash, node): - AbstractEntityCapabilities.__init__(self, caps_cache, caps_hash, node) + AbstractClientCaps.__init__(self, caps_cache, caps_hash, node) def _lookup_in_cache(self): return self._caps_cache[('old', self._node + '#' + self._hash)] @@ -108,12 +116,12 @@ class OldEntityCapabilities(AbstractEntityCapabilities): connection.discoverInfo(jid) -class NullEntityCapabilities(AbstractEntityCapabilities): +class NullClientCaps(AbstractClientCaps): ''' - This is a NULL-Object to streamline caps handling is a client has not + This is a NULL-Object to streamline caps handling if a client has not advertised any caps or has advertised them in an improper way. - Assumes everything is supported. + Assumes (almost) everything is supported. ''' def __init__(self): @@ -123,7 +131,7 @@ class NullEntityCapabilities(AbstractEntityCapabilities): pass def contains_feature(self, feature): - return True + return feature not in FEATURE_BLACKLIST class CapsCache(object): diff --git a/test/test_caps.py b/test/test_caps.py index 3c5dfaa19..32823b078 100644 --- a/test/test_caps.py +++ b/test/test_caps.py @@ -8,7 +8,9 @@ lib.setup_env() from common import helpers from common.contacts import Contact -from common.caps import CapsCache, EntityCapabilities, OldEntityCapabilities +from common.xmpp import NS_MUC, NS_PING, NS_XHTML_IM + +from common.caps import CapsCache, ClientCaps, OldClientCaps from mock import Mock @@ -17,17 +19,14 @@ class CommonCapsTest(unittest.TestCase): def setUp(self): self.caps_method = 'sha-1' - self.caps_hash = 'RNzJvJnTWqczirzu+YF4V8am9ro=' + self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' self.caps = (self.caps_method, self.caps_hash) self.node = "http://gajim.org" self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} - self.muc = 'http://jabber.org/protocol/muc' - self.chatstates = 'http://jabber.org/protocol/chatstates' - self.identities = [self.identity] - self.features = [self.muc] + self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported! # Simulate a filled db db_caps_cache = [ @@ -46,8 +45,8 @@ class TestCapsCache(CommonCapsTest): self.cc[self.caps].identities = self.identities self.cc[self.caps].features = self.features - self.assert_(self.muc in self.cc[self.caps].features) - self.assert_(self.chatstates not in self.cc[self.caps].features) + self.assert_(NS_MUC in self.cc[self.caps].features) + self.assert_(NS_PING not in self.cc[self.caps].features) identities = self.cc[self.caps].identities @@ -79,8 +78,8 @@ class TestCapsCache(CommonCapsTest): self.cc.preload(connection, "test@gajim.org", self.node, self.caps_method, self.caps_hash) - connection.mockCheckCall(0, "discoverInfo", 'test@gajim.org', - 'http://gajim.org#RNzJvJnTWqczirzu+YF4V8am9ro=') + connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", + "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") def test_no_preload_query_if_cashed(self): ''' Preload must not send a query if the data is already cached ''' @@ -97,15 +96,15 @@ class TestCapsCache(CommonCapsTest): caps_hash_method=self.caps_method, caps_hash=self.caps_hash) - self.assertTrue(self.cc.is_supported(contact, self.chatstates), + self.assertTrue(self.cc.is_supported(contact, NS_PING), msg="Assume everything is supported, if we don't have caps") self.cc.initialize_from_db() - self.assertFalse(self.cc.is_supported(contact, self.chatstates), + self.assertFalse(self.cc.is_supported(contact, NS_PING), msg="Must return false on unsupported feature") - self.assertTrue(self.cc.is_supported(contact, self.muc), + self.assertTrue(self.cc.is_supported(contact, NS_MUC), msg="Must return True on supported feature") def test_hash(self): @@ -114,11 +113,11 @@ class TestCapsCache(CommonCapsTest): self.assertEqual(self.caps_hash, computed_hash) -class TestEntityCapabilities(CommonCapsTest): +class TestClientCaps(CommonCapsTest): def setUp(self): CommonCapsTest.setUp(self) - self.caps = EntityCapabilities(self.cc, self.caps_hash, self.node, + self.caps = ClientCaps(self.cc, self.caps_hash, self.node, self.caps_method) def test_no_query_client_of_jid(self): @@ -134,27 +133,33 @@ class TestEntityCapabilities(CommonCapsTest): connection = Mock() self.caps.query_client_of_jid_if_unknown(connection, "test@gajim.org") - connection.mockCheckCall(0, "discoverInfo", 'test@gajim.org', - 'http://gajim.org#RNzJvJnTWqczirzu+YF4V8am9ro=') + connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", + "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") def test_is_supported(self): - self.assertTrue(self.caps.contains_feature(self.chatstates), - msg="Assume everything is supported, if we don't have caps") + self.assertTrue(self.caps.contains_feature(NS_PING), + msg="Assume supported, if we don't have caps") + + self.assertFalse(self.caps.contains_feature(NS_XHTML_IM), + msg="Must not assume blacklisted feature is supported on default") self.cc.initialize_from_db() - self.assertFalse(self.caps.contains_feature(self.chatstates), + self.assertFalse(self.caps.contains_feature(NS_PING), msg="Must return false on unsupported feature") - self.assertTrue(self.caps.contains_feature(self.muc), + self.assertTrue(self.caps.contains_feature(NS_XHTML_IM), msg="Must return True on supported feature") + + self.assertTrue(self.caps.contains_feature(NS_MUC), + msg="Must return True on supported feature") - -class TestOldEntityCapabilities(TestEntityCapabilities): + +class TestOldClientCaps(TestClientCaps): def setUp(self): - TestEntityCapabilities.setUp(self) - self.caps = OldEntityCapabilities(self.cc, self.caps_hash, self.node) + TestClientCaps.setUp(self) + self.caps = OldClientCaps(self.cc, self.caps_hash, self.node) def test_no_query_client_of_jid(self): ''' Client must not be queried if the data is already cached ''' diff --git a/test/test_contacts.py b/test/test_contacts.py index ae25889da..606646aa3 100644 --- a/test/test_contacts.py +++ b/test/test_contacts.py @@ -7,14 +7,12 @@ import lib lib.setup_env() from common.contacts import Contact -from common.caps import NullEntityCapabilities +from common.caps import NullClientCaps from mock import Mock class TestContact(unittest.TestCase): - - def test_supports(self): ''' Test the Entity Capabilities part of the contact instance ''' @@ -35,12 +33,10 @@ class TestContact(unittest.TestCase): self.assertFalse(contact.supports(NS_MUC)) # Test with EntityCapabilites to detect API changes - contact.supports = NullEntityCapabilities() + contact.supports = NullClientCaps() self.assertTrue(contact.supports(NS_MUC), msg="Default behaviour is to support everything on unknown caps") - - if __name__ == "__main__": unittest.main() \ No newline at end of file