From 3295b08b260616bb088aee0cc7edbfe19e71e64b Mon Sep 17 00:00:00 2001
From: Stephan Erb <steve-e@h3c.de>
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 9ef228e337..ec0a9be51d 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 3c5dfaa191..32823b0787 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 ae25889da2..606646aa37 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
-- 
GitLab