Changeset 9508

Show
Ignore:
Timestamp:
04/21/08 00:58:47 (4 months ago)
Author:
asterix
Message:

new XEP-0115 implementation (version 1.5)

Location:
trunk
Files:
13 modified

Legend:

Unmodified
Added
Removed
  • trunk/configure.ac

    r9462 r9508  
    11AC_INIT([Gajim - A Jabber Instant Messager], 
    2                 [0.11.4.3-svn],[http://trac.gajim.org/],[gajim]) 
     2                [0.11.4.4-svn],[http://trac.gajim.org/],[gajim]) 
    33AC_PREREQ([2.59]) 
    44AM_INIT_AUTOMAKE([1.8]) 
  • trunk/src/common/caps.py

    r8926 r9508  
    2121import xmpp.features_nb 
    2222import gajim 
     23import helpers 
    2324 
    2425class CapsCache(object): 
     
    9495                class CacheItem(object): 
    9596                        ''' TODO: logging data into db ''' 
    96                         def __init__(ciself, node, version, ext=None): 
     97                        def __init__(ciself, hash_method, hash): 
    9798                                # cached into db 
    98                                 ciself.node = node 
    99                                 ciself.version = version 
    100                                 ciself.features = set() 
    101                                 ciself.ext = ext 
    102                                 ciself.exts = {} 
    103  
    104                                 # set of tuples: (category, type, name) 
    105                                 # (dictionaries are not hashable, so cannot be in sets) 
    106                                 ciself.identities = set() 
     99                                ciself.hash_method = hash_method 
     100                                ciself.hash = hash 
     101 
     102                                @property 
     103                                def features(): 
     104                                        def fget(self): 
     105                                                return self.getAttr('features') 
     106                                        def fset(self, value): 
     107                                                list_ = [] 
     108                                                for feature in value: 
     109                                                        list_.append(self.__names.setdefault(feature, feature)) 
     110                                                self.setAttr('features', list_) 
     111 
     112                                @property 
     113                                def identities(): 
     114                                        def fget(self): 
     115                                                return self.getAttr('identities') 
     116                                        def fset(self, value): 
     117                                                list_ = [] 
     118                                                for identity in value: 
     119                                                        list_.append(self.__names.setdefault(identity, identity)) 
     120                                                self.setAttr('identities', list_) 
    107121 
    108122                                # not cached into db: 
     
    113127                                ciself.queried = 0 
    114128 
    115                         class CacheQuery(object): 
    116                                 def __init__(cqself, proxied): 
    117                                         cqself.proxied=proxied 
    118  
    119                                 def __getattr__(cqself, obj): 
    120                                         if obj!='exts': return getattr(cqself.proxied[0], obj) 
    121                                         return set(chain(ci.features for ci in cqself.proxied)) 
    122  
    123                         def __getitem__(ciself, exts): 
    124                                 if not exts:    # (), [], None, False, whatever 
    125                                         return ciself 
    126                                 if isinstance(exts, basestring): 
    127                                         exts=(exts,) 
    128                                 if len(exts)==1: 
    129                                         ext=exts[0] 
    130                                         if ext in ciself.exts: 
    131                                                 return ciself.exts[ext] 
    132                                         x=CacheItem(ciself.node, ciself.version, ext) 
    133                                         ciself.exts[ext]=x 
    134                                         return x 
    135                                 proxied = [ciself] 
    136                                 proxied.extend(ciself[(e,)] for e in exts) 
    137                                 return ciself.CacheQuery(proxied) 
    138  
    139129                        def update(ciself, identities, features): 
    140130                                # NOTE: self refers to CapsCache object, not to CacheItem 
    141                                 self.identities=identities 
    142                                 self.features=features 
    143                                 self.logger.add_caps_entry( 
    144                                         ciself.node, ciself.version, ciself.ext, 
     131                                ciself.identities=identities 
     132                                ciself.features=features 
     133                                self.logger.add_caps_entry(ciself.hash_method, ciself.hash, 
    145134                                        identities, features) 
    146135 
     
    148137 
    149138                # prepopulate data which we are sure of; note: we do not log these info 
    150                 gajimnode = 'http://gajim.org/caps' 
    151  
    152                 gajimcaps=self[(gajimnode, '0.11.1')] 
    153                 gajimcaps.category='client' 
    154                 gajimcaps.type='pc' 
    155                 gajimcaps.features=set((xmpp.NS_BYTESTREAM, xmpp.NS_SI, 
    156                         xmpp.NS_FILE, xmpp.NS_MUC, xmpp.NS_COMMANDS, 
    157                         xmpp.NS_DISCO_INFO, xmpp.NS_PING, xmpp.NS_TIME_REVISED)) 
    158                 gajimcaps['cstates'].features=set((xmpp.NS_CHATSTATES,)) 
    159                 gajimcaps['xhtml'].features=set((xmpp.NS_XHTML_IM,)) 
    160  
    161                 # TODO: older gajim versions 
     139 
     140                gajimcaps = self[('sha-1', gajim.caps_hash)] 
     141                gajimcaps.identities = [gajim.gajim_identity] 
     142                gajimcaps.features = gajim.gajim_common_features + \ 
     143                        gajim.gajim_optional_features 
    162144 
    163145                # start logging data from the net 
     
    167149                # get data from logger... 
    168150                if self.logger is not None: 
    169                         for node, ver, ext, identities, features in self.logger.iter_caps_data(): 
    170                                 x=self[(node, ver, ext)] 
    171                                 x.identities=identities 
    172                                 x.features=features 
    173                                 x.queried=2 
     151                        for hash_method, hash, identities, features in \ 
     152                        self.logger.iter_caps_data(): 
     153                                x = self[(hash_method, hash)] 
     154                                x.identities = identities 
     155                                x.features = features 
     156                                x.queried = 2 
    174157 
    175158        def __getitem__(self, caps): 
    176                 node_version = caps[:2] 
    177                 if node_version in self.__cache: 
    178                         return self.__cache[node_version][caps[2]] 
    179                 node, version = self.__names.setdefault(caps[0], caps[0]), caps[1] 
    180                 x=self.__CacheItem(node, version) 
    181                 self.__cache[(node, version)]=x 
     159                if caps in self.__cache: 
     160                        return self.__cache[caps] 
     161                hash_method, hash = caps[0], caps[1] 
     162                x = self.__CacheItem(hash_method, hash) 
     163                self.__cache[(hash_method, hash)] = x 
    182164                return x 
    183165 
    184         def preload(self, account, jid, node, ver, exts): 
     166        def preload(self, con, jid, node, hash_method, hash): 
    185167                ''' Preload data about (node, ver, exts) caps using disco 
    186168                query to jid using proper connection. Don't query if 
    187169                the data is already in cache. ''' 
    188                 q=self[(node, ver, ())] 
    189                 qq=q 
     170                q = self[(hash_method, hash)] 
    190171 
    191172                if q.queried==0: 
    192                         # do query for bare node+version pair 
     173                        # do query for bare node+hash pair 
    193174                        # this will create proper object 
    194175                        q.queried=1 
    195                         account.discoverInfo(jid, '%s#%s' % (node, ver)) 
    196  
    197                 for ext in exts: 
    198                         qq=q[ext] 
    199                         if qq.queried==0: 
    200                                 # do query for node+version+ext triple 
    201                                 qq.queried=1 
    202                                 account.discoverInfo(jid, '%s#%s' % (node, ext)) 
     176                        con.discoverInfo(jid, '%s#%s' % (node, hash)) 
    203177 
    204178gajim.capscache = CapsCache(gajim.logger) 
     
    211185 
    212186                # get the caps element 
    213                 caps=presence.getTag('c') 
    214                 if not caps: return 
    215  
    216                 node, ver=caps['node'], caps['ver'] 
    217                 if node is None or ver is None: 
     187                caps = presence.getTag('c') 
     188                if not caps: 
     189                        return 
     190 
     191                hash_method, node, hash = caps['hash'], caps['node'], caps['ver'] 
     192                if hash_method is None or node is None or hash is None: 
    218193                        # improper caps in stanza, ignoring 
    219194                        return 
    220  
    221                 try: 
    222                         exts=caps['ext'].split(' ') 
    223                 except AttributeError: 
    224                         # no exts means no exts, a perfectly valid case 
    225                         exts=[] 
    226195 
    227196                # we will put these into proper Contact object and ask 
     
    232201 
    233202                # start disco query... 
    234                 gajim.capscache.preload(self, jid, node, ver, exts) 
     203                gajim.capscache.preload(self, jid, node, hash_method, hash) 
    235204 
    236205                contact=gajim.contacts.get_contact_from_full_jid(self.name, jid) 
     
    241210 
    242211                # overwriting old data 
    243                 contact.caps_node=node 
    244                 contact.caps_ver=ver 
    245                 contact.caps_exts=exts 
     212                contact.caps_node = node 
     213                contact.caps_hash_method = hash_method 
     214                contact.caps_hash = hash 
    246215 
    247216        def _capsDiscoCB(self, jid, node, identities, features, data): 
    248                 contact=gajim.contacts.get_contact_from_full_jid(self.name, jid) 
    249                 if not contact: return 
    250                 if not contact.caps_node: return # we didn't asked for that? 
    251                 if not node.startswith(contact.caps_node+'#'): return 
    252                 node, ext = node.split('#', 1) 
    253                 if ext==contact.caps_ver:       # this can be also version (like '0.9') 
    254                         exts=None 
    255                 else: 
    256                         exts=(ext,) 
     217                contact = gajim.contacts.get_contact_from_full_jid(self.name, jid) 
     218                if not contact: 
     219                        return 
     220                if not contact.caps_node: 
     221                        return # we didn't asked for that? 
     222                if not node.startswith(contact.caps_node + '#'): 
     223                        return 
     224                node, hash = node.split('#', 1) 
     225                computed_hash = helpers.compute_caps_hash(identities, features, 
     226                        contact.caps_hash_method) 
     227                if computed_hash != hash: 
     228                        # wrong hash, forget it 
     229                        contact.caps_node = '' 
     230                        contact.caps_hash_method = '' 
     231                        contact.caps_hash = '' 
     232                        return 
    257233 
    258234                # if we don't have this info already... 
    259                 caps=gajim.capscache[(node, contact.caps_ver, exts)] 
    260                 if caps.queried==2: return 
    261  
    262                 identities=set((i['category'], i['type'], i.get('name')) for i in identities) 
     235                caps = gajim.capscache[(contact.caps_hash_method, hash)] 
     236                if caps.queried == 2: 
     237                        return 
     238 
    263239                caps.update(identities, features) 
    264  
  • trunk/src/common/check_paths.py

    r9464 r9508  
    8383 
    8484                CREATE TABLE caps_cache ( 
    85                         node TEXT, 
    86                         ver TEXT, 
    87                         ext TEXT, 
     85                        hash_method TEXT, 
     86                        hash TEXT, 
    8887                        data BLOB); 
    8988 
  • trunk/src/common/connection_handlers.py

    r9494 r9508  
    668668                iq.setAttr('id', id) 
    669669                query = iq.setTag('query') 
    670                 query.setAttr('node','http://gajim.org/caps#' + gajim.version.split('-', 
     670                query.setAttr('node','http://gajim.org#' + gajim.version.split('-', 
    671671                        1)[0]) 
    672672                for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, 
     
    745745                        if node: 
    746746                                q.setAttr('node', node) 
    747                         q.addChild('identity', attrs = {'type': 'pc', 'category': 'client', 
    748                                 'name': 'Gajim'}) 
     747                        q.addChild('identity', attrs = gajim.gajim_identity) 
    749748                        extension = None 
    750749                        if node and node.find('#') != -1: 
    751750                                extension = node[node.index('#') + 1:] 
    752                         client_version = 'http://gajim.org/caps#' + gajim.version.split('-', 
    753                                 1)[0] 
     751                        client_version = 'http://gajim.org#' + gajim.caps_hash 
    754752 
    755753                        if node in (None, client_version): 
    756                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_BYTESTREAM}) 
    757                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_SI}) 
    758                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE}) 
    759                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) 
    760                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC_USER}) 
    761                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC_ADMIN}) 
    762                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC_OWNER}) 
    763                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC_CONFIG}) 
    764                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_COMMANDS}) 
    765                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_DISCO_INFO}) 
    766                                 q.addChild('feature', attrs = {'var': 'ipv6'}) 
    767                                 q.addChild('feature', attrs = {'var': 'jabber:iq:gateway'}) 
    768                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_LAST}) 
    769                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_PRIVACY}) 
    770                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_PRIVATE}) 
    771                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_REGISTER}) 
    772                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_VERSION}) 
    773                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_DATA}) 
    774                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_ENCRYPTED}) 
    775                                 q.addChild('feature', attrs = {'var': 'msglog'}) 
    776                                 q.addChild('feature', attrs = {'var': 'sslc2s'}) 
    777                                 q.addChild('feature', attrs = {'var': 'stringprep'}) 
    778                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_PING}) 
    779                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_ACTIVITY}) 
    780                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_ACTIVITY + '+notify'}) 
    781                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_TUNE}) 
    782                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_TUNE + '+notify'}) 
    783                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD}) 
    784                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD + '+notify'}) 
    785                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_ESESSION_INIT}) 
    786  
    787                         if (node is None or extension == 'cstates') and gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': 
    788                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_CHATSTATES}) 
    789  
    790                         if (node is None or extension == 'xhtml') and not gajim.config.get('ignore_incoming_xhtml'): 
    791                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM}) 
    792  
    793                         if node is None: 
    794                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_PING}) 
    795                                 q.addChild('feature', attrs = {'var': common.xmpp.NS_TIME_REVISED}) 
     754                                for f in gajim.gajim_common_features: 
     755                                        q.addChild('feature', attrs = {'var': f}) 
     756                                for f in gajim.gajim_optional_features: 
     757                                        q.addChild('feature', attrs = {'var': f}) 
    796758 
    797759                        if q.getChildren(): 
     
    893855                ''' advertise our capabilities in presence stanza (xep-0115)''' 
    894856                c = p.setTag('c', namespace = common.xmpp.NS_CAPS) 
    895                 c.setAttr('node', 'http://gajim.org/caps') 
    896                 ext = [] 
    897                 if not gajim.config.get('ignore_incoming_xhtml'): 
    898                         ext.append('xhtml') 
    899                 if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': 
    900                         ext.append('cstates') 
    901  
    902                 if len(ext): 
    903                         c.setAttr('ext', ' '.join(ext)) 
    904                 c.setAttr('ver', gajim.version.split('-', 1)[0]) 
     857                c.setAttr('hash', 'sha-1') 
     858                c.setAttr('node', 'http://gajim.org') 
     859                c.setAttr('ver', gajim.caps_hash) 
    905860                return p 
    906861         
  • trunk/src/common/contacts.py

    r9497 r9508  
    4545                # every time it gets these from presence stanzas 
    4646                self.caps_node = None 
    47                 self.caps_ver = None 
    48                 self.caps_exts = None 
     47                self.caps_hash_method = None 
     48                self.caps_hash = None 
    4949 
    5050                # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html 
  • trunk/src/common/defs.py

    r9462 r9508  
    33datadir = '../' 
    44 
    5 version = '0.11.4.3-svn' 
     5version = '0.11.4.4-svn' 
    66 
    77import sys, os.path 
  • trunk/src/common/gajim.py

    r9502 r9508  
    2828from contacts import Contacts 
    2929from events import Events 
     30import xmpp 
    3031 
    3132try: 
     
    164165        if system('gpg -h >/dev/null 2>&1'): 
    165166                HAVE_GPG = False 
     167 
     168gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'} 
     169gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, 
     170        xmpp.NS_FILE, xmpp.NS_MUC, xmpp.NS_MUC_USER, 
     171        xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER, 
     172        xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, 
     173        xmpp.NS_DISCO_INFO, 'ipv6', 'jabber:iq:gateway', xmpp.NS_LAST, 
     174        xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, xmpp.NS_REGISTER, 
     175        xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 
     176        'msglog', 'sslc2s', 'stringprep', xmpp.NS_PING, 
     177        xmpp.NS_TIME_REVISED] 
     178# Optional features gajim supports 
     179gajim_optional_features = [] 
     180 
     181caps_hash = '' 
    166182 
    167183def get_nick_from_jid(jid): 
  • trunk/src/common/helpers.py

    r9488 r9508  
    3131import select 
    3232import sha 
     33import hashlib 
     34import base64 
    3335import sys 
    3436from encodings.punycode import punycode_encode 
     
    3941from i18n import ngettext 
    4042from xmpp_stringprep import nodeprep, resourceprep, nameprep 
    41  
     43import xmpp 
    4244 
    4345if sys.platform == 'darwin': 
     
    12001202        return keyID 
    12011203 
     1204def sort_identities_func(i1, i2): 
     1205        cat1 = i1['category'] 
     1206        cat2 = i2['category'] 
     1207        if cat1 < cat2: 
     1208                return -1 
     1209        if cat1 > cat2: 
     1210                return 1 
     1211        if i1.has_key('type'): 
     1212                type1 = i1['type'] 
     1213        else: 
     1214                type1 = '' 
     1215        if i2.has_key('type'): 
     1216                type2 = i2['type'] 
     1217        else: 
     1218                type2 = '' 
     1219        if type1 < type2: 
     1220                return -1 
     1221        if type1 > type2: 
     1222                return 1 
     1223        if i1.has_key('xml:lang'): 
     1224                lang1 = i1['xml:lang'] 
     1225        else: 
     1226                lang1 = '' 
     1227        if i2.has_key('xml:lang'): 
     1228                lang2 = i2['xml:lang'] 
     1229        else: 
     1230                lang2 = '' 
     1231        if lang1 < lang2: 
     1232                return -1 
     1233        if lang1 > lang2: 
     1234                return 1 
     1235        return 0 
     1236 
     1237def compute_caps_hash(identities, features, hash_method='sha-1'): 
     1238        S = '' 
     1239        identities.sort(cmp=sort_identities_func) 
     1240        for i in identities: 
     1241                c = i['category'] 
     1242                if i.has_key('type'): 
     1243                        type_ = i['type'] 
     1244                else: 
     1245                        type_ = '' 
     1246                if i.has_key('xml:lang'): 
     1247                        lang = i['xml:lang'] 
     1248                else: 
     1249                        lang = '' 
     1250                if i.has_key('name'): 
     1251                        name = i['name'] 
     1252                else: 
     1253                        name = '' 
     1254                S += '%s/%s/%s/%s<' % (c, type_, lang, name) 
     1255        features.sort() 
     1256        for f in features: 
     1257                S += '%s<' % f 
     1258        if hash_method == 'sha-1': 
     1259                hash = hashlib.sha1(S) 
     1260        elif hash_method == 'md5': 
     1261                hash = hashlib.md5(S) 
     1262        else: 
     1263                return '' 
     1264        return base64.b64encode(hash.digest()) 
     1265 
     1266def update_optional_features(): 
     1267        gajim.gajim_optional_features = [] 
     1268        if gajim.config.get('publish_mood'): 
     1269                gajim.gajim_optional_features.append(xmpp.NS_MOOD) 
     1270        if gajim.config.get('subscribe_mood'): 
     1271                gajim.gajim_optional_features.append(xmpp.NS_MOOD + '+notify') 
     1272        if gajim.config.get('publish_activity'): 
     1273                gajim.gajim_optional_features.append(xmpp.NS_ACTIVITY) 
     1274        if gajim.config.get('subscribe_activity'): 
     1275                gajim.gajim_optional_features.append(xmpp.NS_ACTIVITY + '+notify') 
     1276        if gajim.config.get('publish_tune'): 
     1277                gajim.gajim_optional_features.append(xmpp.NS_TUNE) 
     1278        if gajim.config.get('subscribe_tune'): 
     1279                gajim.gajim_optional_features.append(xmpp.NS_TUNE + '+notify') 
     1280        if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': 
     1281                gajim.gajim_optional_features.append(xmpp.NS_CHATSTATES) 
     1282        if not gajim.config.get('ignore_incoming_xhtml'): 
     1283                gajim.gajim_optional_features.append(xmpp.NS_XHTML_IM) 
     1284        if gajim.HAVE_PYCRYPTO: 
     1285                gajim.gajim_optional_features.append(xmpp.NS_ESESSION_INIT) 
     1286        gajim.caps_hash = compute_caps_hash([gajim.gajim_identity], 
     1287                gajim.gajim_common_features + gajim.gajim_optional_features) 
     1288        # re-send presence with new hash 
     1289        for account in gajim.connections: 
     1290                connected = gajim.connections[account].connected 
     1291                if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': 
     1292                        gajim.connections[account].change_status(gajim.SHOW_LIST[connected], 
     1293                                gajim.connections[account].status) 
  • trunk/src/common/logger.py

    r9481 r9508  
    699699                #tmp, self.con.text_factory = self.con.text_factory, str 
    700700                try: 
    701                         self.cur.execute('''SELECT node, ver, ext, data FROM caps_cache;'''); 
     701                        self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;'); 
    702702                except sqlite.OperationalError: 
    703703                        # might happen when there's no caps_cache table yet 
     
    706706                        return 
    707707                #self.con.text_factory = tmp 
    708  
    709                 for node, ver, ext, data in self.cur: 
     708                for hash_method, hash, data in self.cur: 
    710709                        # for each row: unpack the data field 
    711710                        # (format: (category, type, name, category, type, name, ... 
    712711                        #   ..., 'FEAT', feature1, feature2, ...).join(' ')) 
    713712                        # NOTE: if there's a need to do more gzip, put that to a