Move caps code into own module

This commit is contained in:
Philipp Hörist 2018-07-22 19:12:52 +02:00
parent 78e4e20d49
commit a943a35a5d
9 changed files with 250 additions and 227 deletions

View File

@ -231,7 +231,7 @@ class ChatControl(ChatControlBase):
self._nec_update_avatar) self._nec_update_avatar)
app.ged.register_event_handler('chatstate-received', ged.GUI1, app.ged.register_event_handler('chatstate-received', ged.GUI1,
self._nec_chatstate_received) self._nec_chatstate_received)
app.ged.register_event_handler('caps-received', ged.GUI1, app.ged.register_event_handler('caps-update', ged.GUI1,
self._nec_caps_received) self._nec_caps_received)
app.ged.register_event_handler('message-sent', ged.OUT_POSTCORE, app.ged.register_event_handler('message-sent', ged.OUT_POSTCORE,
self._message_sent) self._message_sent)
@ -1154,7 +1154,7 @@ class ChatControl(ChatControlBase):
self._nec_update_avatar) self._nec_update_avatar)
app.ged.remove_event_handler('chatstate-received', ged.GUI1, app.ged.remove_event_handler('chatstate-received', ged.GUI1,
self._nec_chatstate_received) self._nec_chatstate_received)
app.ged.remove_event_handler('caps-received', ged.GUI1, app.ged.remove_event_handler('caps-update', ged.GUI1,
self._nec_caps_received) self._nec_caps_received)
app.ged.remove_event_handler('message-sent', ged.OUT_POSTCORE, app.ged.remove_event_handler('message-sent', ged.OUT_POSTCORE,
self._message_sent) self._message_sent)

View File

@ -29,24 +29,18 @@
## ##
import operator import operator
from time import time as time_time from time import time as time_time
from gi.repository import GLib
import nbxmpp import nbxmpp
from gajim.common import caps_cache as capscache
from gajim.common import modules from gajim.common import modules
from gajim.common import helpers from gajim.common import helpers
from gajim.common import app from gajim.common import app
from gajim.common import jingle_xtls from gajim.common import jingle_xtls
from gajim.common.caps_cache import muc_caps_cache from gajim.common.caps_cache import muc_caps_cache
from gajim.common.protocol.caps import ConnectionCaps
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
from gajim.common.protocol.bytestream import ConnectionIBBytestream from gajim.common.protocol.bytestream import ConnectionIBBytestream
from gajim.common.connection_handlers_events import * from gajim.common.connection_handlers_events import *
from gajim.common.modules.misc import parse_eme
from gajim.common import ged from gajim.common import ged
from gajim.common.nec import NetworkEvent from gajim.common.nec import NetworkEvent
@ -455,7 +449,7 @@ class ConnectionHandlersBase:
return sess return sess
class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
ConnectionCaps, ConnectionHandlersBase, ConnectionHandlersBase,
ConnectionJingle, ConnectionIBBytestream): ConnectionJingle, ConnectionIBBytestream):
def __init__(self): def __init__(self):
ConnectionSocks5Bytestream.__init__(self) ConnectionSocks5Bytestream.__init__(self)
@ -464,9 +458,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
# Handle presences BEFORE caps # Handle presences BEFORE caps
app.nec.register_incoming_event(PresenceReceivedEvent) app.nec.register_incoming_event(PresenceReceivedEvent)
ConnectionCaps.__init__(self, account=self.name,
capscache=capscache.capscache,
client_caps_factory=capscache.create_suitable_client_caps)
ConnectionJingle.__init__(self) ConnectionJingle.__init__(self)
ConnectionHandlersBase.__init__(self) ConnectionHandlersBase.__init__(self)
@ -502,7 +493,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
def cleanup(self): def cleanup(self):
ConnectionHandlersBase.cleanup(self) ConnectionHandlersBase.cleanup(self)
ConnectionCaps.cleanup(self)
app.ged.remove_event_handler('roster-set-received', app.ged.remove_event_handler('roster-set-received',
ged.CORE, self._nec_roster_set_received) ged.CORE, self._nec_roster_set_received)
app.ged.remove_event_handler('roster-received', ged.CORE, app.ged.remove_event_handler('roster-received', ged.CORE,
@ -681,14 +671,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
self.connection.SendAndCallForResponse(iq, self._on_bob_received, self.connection.SendAndCallForResponse(iq, self._on_bob_received,
{'cid': cid}) {'cid': cid})
def _presenceCB(self, con, prs):
"""
Called when we receive a presence
"""
log.debug('PresenceCB')
app.nec.push_incoming_event(NetworkEvent('raw-pres-received',
conn=self, stanza=prs))
def _nec_subscribe_presence_received(self, obj): def _nec_subscribe_presence_received(self, obj):
account = obj.conn.name account = obj.conn.name
if account != self.name: if account != self.name:
@ -918,7 +900,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
def _register_handlers(self, con, con_type): def _register_handlers(self, con, con_type):
# try to find another way to register handlers in each class # try to find another way to register handlers in each class
# that defines handlers # that defines handlers
con.RegisterHandler('presence', self._presenceCB)
con.RegisterHandler('iq', self._rosterSetCB, 'set', nbxmpp.NS_ROSTER) con.RegisterHandler('iq', self._rosterSetCB, 'set', nbxmpp.NS_ROSTER)
con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI) con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI) con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI)

View File

@ -900,48 +900,6 @@ class ConnectionLostEvent(nec.NetworkIncomingEvent):
show='offline')) show='offline'))
return True return True
class CapsPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
PresenceHelperEvent):
name = 'caps-presence-received'
base_network_events = ['raw-pres-received']
def _extract_caps_from_presence(self):
caps_tag = self.stanza.getTag('c', namespace=nbxmpp.NS_CAPS)
if caps_tag:
self.hash_method = caps_tag['hash']
self.node = caps_tag['node']
self.caps_hash = caps_tag['ver']
else:
self.hash_method = self.node = self.caps_hash = None
def generate(self):
self.conn = self.base_event.conn
self.stanza = self.base_event.stanza
try:
self.get_jid_resource()
except Exception:
return
self._generate_ptype()
self._generate_show()
self._extract_caps_from_presence()
return True
class CapsDiscoReceivedEvent(nec.NetworkIncomingEvent):
name = 'caps-disco-received'
base_network_events = []
class CapsReceivedEvent(nec.NetworkIncomingEvent):
name = 'caps-received'
base_network_events = ['caps-presence-received', 'caps-disco-received']
def generate(self):
self.conn = self.base_event.conn
self.fjid = self.base_event.fjid
self.jid = self.base_event.jid
self.resource = self.base_event.resource
self.client_caps = self.base_event.client_caps
return True
class GPGTrustKeyEvent(nec.NetworkIncomingEvent): class GPGTrustKeyEvent(nec.NetworkIncomingEvent):
name = 'gpg-trust-key' name = 'gpg-trust-key'
base_network_events = [] base_network_events = []

View File

@ -0,0 +1,153 @@
# Copyright (C) 2009 Stephan Erb <steve-e AT h3c.de>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0115: Entity Capabilities
import logging
import nbxmpp
from gajim.common import caps_cache
from gajim.common import app
from gajim.common.nec import NetworkEvent
from gajim.common.modules.presence import parse_show
from gajim.common.modules.presence import parse_type
log = logging.getLogger('gajim.c.m.caps')
class Caps:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = [
('presence', self._presence_received, '', nbxmpp.NS_CAPS)
]
self._capscache = caps_cache.capscache
self._create_suitable_client_caps = caps_cache.create_suitable_client_caps
def _presence_received(self, con, stanza):
hash_method = node = caps_hash = None
caps = stanza.getTag('c', namespace=nbxmpp.NS_CAPS)
if caps is not None:
hash_method = caps['hash']
node = caps['node']
caps_hash = caps['ver']
from_ = stanza.getFrom()
full_jid = str(from_)
show = parse_show(stanza)
type_ = parse_type(stanza)
log.info('Received from %s, type: %s, method: %s, node: %s, hash: %s',
from_, stanza.getType(), hash_method, node, caps_hash)
client_caps = self._create_suitable_client_caps(
node, caps_hash, hash_method, full_jid)
# Type is None means 'available'
if stanza.getType() is None and client_caps._hash_method == 'no':
self._capscache.forget_caps(client_caps)
client_caps = self._create_suitable_client_caps(
node, caps_hash, hash_method)
else:
self._capscache.query_client_of_jid_if_unknown(
self._con, full_jid, client_caps)
self._update_client_caps_of_contact(from_, client_caps)
# Event is only used by ClientIcons Plugin
app.nec.push_incoming_event(NetworkEvent(
'caps-presence-received',
conn=self._con,
fjid=full_jid,
jid=from_.getStripped(),
resource=from_.getResource(),
hash_method=hash_method,
node=node,
caps_hash=caps_hash,
client_caps=client_caps,
show=show,
ptype=type_,
stanza=stanza))
app.nec.push_incoming_event(NetworkEvent('caps-update',
conn=self._con,
fjid=full_jid,
jid=from_.getStripped()))
def _update_client_caps_of_contact(self, from_, client_caps):
contact = self._get_contact_or_gc_contact_for_jid(from_)
if contact is not None:
contact.client_caps = client_caps
else:
log.warning('Received Caps from unknown contact %s' % from_)
def _get_contact_or_gc_contact_for_jid(self, from_):
contact = app.contacts.get_contact_from_full_jid(self._account,
str(from_))
if contact is None:
room_jid, resource = from_.getStripped(), from_.getResource()
contact = app.contacts.get_gc_contact(
self._account, room_jid, resource)
return contact
def contact_info_received(self, from_, identities, features, data, node):
"""
callback to update our caps cache with queried information after
we have retrieved an unknown caps hash via a disco
"""
bare_jid = from_.getStripped()
contact = self._get_contact_or_gc_contact_for_jid(from_)
if not contact:
log.info('Received Disco from unknown contact %s' % from_)
return
lookup = contact.client_caps.get_cache_lookup_strategy()
cache_item = lookup(self._capscache)
if cache_item.is_valid():
# we already know that the hash is fine and have already cached
# the identities and features
return
else:
validate = contact.client_caps.get_hash_validation_strategy()
hash_is_valid = validate(identities, features, data)
if hash_is_valid:
cache_item.set_and_store(identities, features)
else:
node = caps_hash = hash_method = None
contact.client_caps = self._create_suitable_client_caps(
node, caps_hash, hash_method)
log.warning(
'Computed and retrieved caps hash differ. Ignoring '
'caps of contact %s' % contact.get_full_jid())
app.nec.push_incoming_event(
NetworkEvent('caps-update',
conn=self._con,
fjid=str(from_),
jid=bare_jid))
def get_instance(*args, **kwargs):
return Caps(*args, **kwargs), 'Caps'

View File

@ -39,7 +39,7 @@ class Discovery:
] ]
def disco_contact(self, jid, node=None): def disco_contact(self, jid, node=None):
success_cb = self._con._nec_agent_info_received_caps success_cb = self._con.get_module('Caps').contact_info_received
self._disco(nbxmpp.NS_DISCO_INFO, jid, node, success_cb, None) self._disco(nbxmpp.NS_DISCO_INFO, jid, node, success_cb, None)
def disco_items(self, jid, node=None, success_cb=None, error_cb=None): def disco_items(self, jid, node=None, success_cb=None, error_cb=None):

View File

@ -0,0 +1,70 @@
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# Presence handler
import logging
from gajim.common import app
from gajim.common.nec import NetworkEvent
log = logging.getLogger('gajim.c.m.presence')
class Presence:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = [('presence', self._presence_received)]
def _presence_received(self, con, stanza):
log.info('Received from %s', stanza.getFrom())
app.nec.push_incoming_event(
NetworkEvent('raw-pres-received',
conn=self._con,
stanza=stanza))
def parse_show(stanza):
show = stanza.getShow()
type_ = parse_type(stanza)
if show is None and type_ is None:
return 'online'
if type_ == 'unavailable':
return 'offline'
if show not in (None, 'chat', 'away', 'xa', 'dnd'):
log.warning('Invalid show element: %s', stanza)
if type_ is None:
return 'online'
return 'offline'
if show is None:
return 'online'
return show
def parse_type(stanza):
type_ = stanza.getType()
if type_ not in (None, 'unavailable', 'error', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'):
log.warning('Invalid type: %s', stanza)
return None
return type_
def get_instance(*args, **kwargs):
return Presence(*args, **kwargs), 'Presence'

View File

@ -1,121 +0,0 @@
# -*- coding:utf-8 -*-
## src/common/protocol/caps.py
##
## Copyright (C) 2009 Stephan Erb <steve-e AT h3c.de>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
"""
Module containing the network portion of XEP-115 (Entity Capabilities)
"""
import logging
log = logging.getLogger('gajim.c.p.caps')
from gajim.common import app
from gajim.common import ged
from gajim.common.connection_handlers_events import CapsPresenceReceivedEvent, \
CapsDiscoReceivedEvent, CapsReceivedEvent
class ConnectionCaps(object):
def __init__(self, account, capscache, client_caps_factory):
self._account = account
self._capscache = capscache
self._create_suitable_client_caps = client_caps_factory
app.nec.register_incoming_event(CapsPresenceReceivedEvent)
app.nec.register_incoming_event(CapsReceivedEvent)
app.ged.register_event_handler('caps-presence-received', ged.GUI1,
self._nec_caps_presence_received)
def cleanup(self):
app.ged.remove_event_handler('caps-presence-received', ged.GUI1,
self._nec_caps_presence_received)
def caps_change_account_name(self, new_name):
self._account = new_name
def _nec_caps_presence_received(self, obj):
if obj.conn.name != self._account:
return
obj.client_caps = self._create_suitable_client_caps(obj.node,
obj.caps_hash, obj.hash_method, obj.fjid)
if obj.show == 'offline' and obj.client_caps._hash_method == 'no':
self._capscache.forget_caps(obj.client_caps)
obj.client_caps = self._create_suitable_client_caps(obj.node,
obj.caps_hash, obj.hash_method)
else:
self._capscache.query_client_of_jid_if_unknown(self, obj.fjid,
obj.client_caps)
self._update_client_caps_of_contact(obj)
def _update_client_caps_of_contact(self, obj):
contact = self._get_contact_or_gc_contact_for_jid(obj.fjid)
if contact:
contact.client_caps = obj.client_caps
else:
log.info('Received Caps from unknown contact %s' % obj.fjid)
def _get_contact_or_gc_contact_for_jid(self, jid):
contact = app.contacts.get_contact_from_full_jid(self._account, jid)
if contact is None:
room_jid, nick = app.get_room_and_nick_from_fjid(jid)
contact = app.contacts.get_gc_contact(self._account, room_jid, nick)
return contact
def _nec_agent_info_received_caps(self, from_, identities, features,
data, node):
"""
callback to update our caps cache with queried information after
we have retrieved an unknown caps hash and issued a disco
"""
fjid = str(from_)
bare_jid = from_.getStripped()
resource = from_.getResource()
contact = self._get_contact_or_gc_contact_for_jid(fjid)
if not contact:
log.info('Received Disco from unknown contact %s' % fjid)
return
lookup = contact.client_caps.get_cache_lookup_strategy()
cache_item = lookup(self._capscache)
if cache_item.is_valid():
# we already know that the hash is fine and have already cached
# the identities and features
return
else:
validate = contact.client_caps.get_hash_validation_strategy()
hash_is_valid = validate(identities, features, data)
if hash_is_valid:
cache_item.set_and_store(identities, features)
else:
node = caps_hash = hash_method = None
contact.client_caps = self._create_suitable_client_caps(
node, caps_hash, hash_method)
log.info('Computed and retrieved caps hash differ.'
'Ignoring caps of contact %s' % contact.get_full_jid())
app.nec.push_incoming_event(
CapsDiscoReceivedEvent(None,
conn=self,
fjid=fjid,
jid=bare_jid,
resource=resource,
client_caps=contact.client_caps))

View File

@ -169,7 +169,7 @@ class PrivateChatControl(ChatControl):
self.TYPE_ID = 'pm' self.TYPE_ID = 'pm'
app.ged.register_event_handler('update-gc-avatar', ged.GUI1, app.ged.register_event_handler('update-gc-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)
app.ged.register_event_handler('caps-received', ged.GUI1, app.ged.register_event_handler('caps-update', ged.GUI1,
self._nec_caps_received_pm) self._nec_caps_received_pm)
app.ged.register_event_handler('gc-presence-received', ged.GUI1, app.ged.register_event_handler('gc-presence-received', ged.GUI1,
self._nec_gc_presence_received) self._nec_gc_presence_received)
@ -181,7 +181,7 @@ class PrivateChatControl(ChatControl):
super(PrivateChatControl, self).shutdown() super(PrivateChatControl, self).shutdown()
app.ged.remove_event_handler('update-gc-avatar', ged.GUI1, app.ged.remove_event_handler('update-gc-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)
app.ged.remove_event_handler('caps-received', ged.GUI1, app.ged.remove_event_handler('caps-update', ged.GUI1,
self._nec_caps_received_pm) self._nec_caps_received_pm)
app.ged.remove_event_handler('gc-presence-received', ged.GUI1, app.ged.remove_event_handler('gc-presence-received', ged.GUI1,
self._nec_gc_presence_received) self._nec_gc_presence_received)

View File

@ -1,7 +1,11 @@
''' '''
Tests for caps network coding Tests for caps network coding
''' '''
import unittest import unittest
from unittest.mock import MagicMock
import nbxmpp
import lib import lib
lib.setup_env() lib.setup_env()
@ -10,64 +14,42 @@ from gajim.common import app
from gajim.common import nec from gajim.common import nec
from gajim.common import ged from gajim.common import ged
from gajim.common import caps_cache from gajim.common import caps_cache
from gajim.common.connection_handlers import ConnectionHandlers from gajim.common.modules.caps import Caps
from gajim.common.protocol import caps
from gajim.common.contacts import Contact
from gajim.common.connection_handlers_events import CapsPresenceReceivedEvent
from mock import Mock
import nbxmpp
class TestableConnectionCaps(ConnectionHandlers, caps.ConnectionCaps):
def __init__(self, *args, **kwargs):
self.name = 'account'
self._mocked_contacts = {}
caps.ConnectionCaps.__init__(self, *args, **kwargs)
def _get_contact_or_gc_contact_for_jid(self, jid):
"""
Overwrite to decouple form contact handling
"""
if jid not in self._mocked_contacts:
self._mocked_contacts[jid] = Mock(realClass=Contact)
self._mocked_contacts[jid].jid = jid
return self._mocked_contacts[jid]
def discoverInfo(self, *args, **kwargs):
pass
def get_mocked_contact_for_jid(self, jid):
return self._mocked_contacts[jid]
class TestConnectionCaps(unittest.TestCase): class TestConnectionCaps(unittest.TestCase):
def setUp(self): def setUp(self):
app.contacts.add_account('account')
contact = app.contacts.create_contact(
'user@server.com', 'account', resource='a')
app.contacts.add_contact('account', contact)
app.nec = nec.NetworkEventsController() app.nec = nec.NetworkEventsController()
app.ged.register_event_handler('caps-presence-received', ged.GUI2, app.ged.register_event_handler(
'caps-presence-received', ged.GUI2,
self._nec_caps_presence_received) self._nec_caps_presence_received)
self.module = Caps(MagicMock())
self.module._account = 'account'
self.module._capscache = MagicMock()
def _nec_caps_presence_received(self, obj): def _nec_caps_presence_received(self, obj):
self.assertFalse(isinstance(obj.client_caps, caps_cache.NullClientCaps), self.assertTrue(
msg="On receive of proper caps, we must not use the fallback") isinstance(obj.client_caps, caps_cache.ClientCaps),
msg="On receive of valid caps, ClientCaps should be returned")
def test_capsPresenceCB(self): def test_capsPresenceCB(self):
fjid = "user@server.com/a" fjid = "user@server.com/a"
connection_caps = TestableConnectionCaps("account", Mock(),
caps_cache.create_suitable_client_caps)
contact = connection_caps._get_contact_or_gc_contact_for_jid(fjid)
xml = """<presence from='user@server.com/a' to='%s' id='123'> xml = """<presence from='user@server.com/a' to='%s' id='123'>
<c node='http://gajim.org' ver='pRCD6cgQ4SDqNMCjdhRV6TECx5o=' <c node='http://gajim.org' ver='pRCD6cgQ4SDqNMCjdhRV6TECx5o='
hash='sha-1' xmlns='http://jabber.org/protocol/caps'/> hash='sha-1' xmlns='http://jabber.org/protocol/caps'/>
</presence> </presence>
""" % (fjid) """ % (fjid)
msg = nbxmpp.protocol.Presence(node=nbxmpp.simplexml.XML2Node(xml)) msg = nbxmpp.protocol.Presence(node=nbxmpp.simplexml.XML2Node(xml))
connection_caps._presenceCB(None, msg) self.module._presence_received(None, msg)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()