1# This file is part of Gajim. 2# 3# Gajim is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published 5# by the Free Software Foundation; version 3 only. 6# 7# Gajim is distributed in the hope that it will be useful, 8# but WITHOUT ANY WARRANTY; without even the implied warranty of 9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10# GNU General Public License for more details. 11# 12# You should have received a copy of the GNU General Public License 13# along with Gajim. If not, see <http://www.gnu.org/licenses/>. 14 15# XEP-0030: Service Discovery 16 17import nbxmpp 18from nbxmpp.namespaces import Namespace 19from nbxmpp.structs import StanzaHandler 20from nbxmpp.errors import StanzaError 21from nbxmpp.errors import is_error 22 23from gajim.common import app 24from gajim.common.nec import NetworkIncomingEvent 25from gajim.common.nec import NetworkEvent 26from gajim.common.modules.util import as_task 27from gajim.common.modules.base import BaseModule 28 29 30class Discovery(BaseModule): 31 32 _nbxmpp_extends = 'Discovery' 33 _nbxmpp_methods = [ 34 'disco_info', 35 'disco_items', 36 ] 37 38 def __init__(self, con): 39 BaseModule.__init__(self, con) 40 41 self.handlers = [ 42 StanzaHandler(name='iq', 43 callback=self._answer_disco_info, 44 typ='get', 45 ns=Namespace.DISCO_INFO), 46 StanzaHandler(name='iq', 47 callback=self._answer_disco_items, 48 typ='get', 49 ns=Namespace.DISCO_ITEMS), 50 ] 51 52 self._account_info = None 53 self._server_info = None 54 55 @property 56 def account_info(self): 57 return self._account_info 58 59 @property 60 def server_info(self): 61 return self._server_info 62 63 def discover_server_items(self): 64 server = self._con.get_own_jid().domain 65 self.disco_items(server, callback=self._server_items_received) 66 67 def _server_items_received(self, task): 68 try: 69 result = task.finish() 70 except StanzaError as error: 71 self._log.warning('Server disco failed') 72 self._log.error(error) 73 return 74 75 self._log.info('Server items received') 76 self._log.debug(result) 77 for item in result.items: 78 if item.node is not None: 79 # Only disco components 80 continue 81 self.disco_info(item.jid, callback=self._server_items_info_received) 82 83 def _server_items_info_received(self, task): 84 try: 85 result = task.finish() 86 except StanzaError as error: 87 self._log.warning('Server item disco info failed') 88 self._log.warning(error) 89 return 90 91 self._log.info('Server item info received: %s', result.jid) 92 self._parse_transports(result) 93 try: 94 self._con.get_module('MUC').pass_disco(result) 95 self._con.get_module('HTTPUpload').pass_disco(result) 96 self._con.get_module('Bytestream').pass_disco(result) 97 except nbxmpp.NodeProcessed: 98 pass 99 100 app.nec.push_incoming_event( 101 NetworkIncomingEvent('server-disco-received')) 102 103 def discover_account_info(self): 104 own_jid = self._con.get_own_jid().bare 105 self.disco_info(own_jid, callback=self._account_info_received) 106 107 def _account_info_received(self, task): 108 try: 109 result = task.finish() 110 except StanzaError as error: 111 self._log.warning('Account disco info failed') 112 self._log.warning(error) 113 return 114 115 self._log.info('Account info received: %s', result.jid) 116 117 self._account_info = result 118 119 self._con.get_module('MAM').pass_disco(result) 120 self._con.get_module('PEP').pass_disco(result) 121 self._con.get_module('PubSub').pass_disco(result) 122 self._con.get_module('Bookmarks').pass_disco(result) 123 self._con.get_module('VCardAvatars').pass_disco(result) 124 125 self._con.get_module('Caps').update_caps() 126 127 def discover_server_info(self): 128 # Calling this method starts the connect_maschine() 129 server = self._con.get_own_jid().domain 130 self.disco_info(server, callback=self._server_info_received) 131 132 def _server_info_received(self, task): 133 try: 134 result = task.finish() 135 except StanzaError as error: 136 self._log.error('Server disco info failed') 137 self._log.error(error) 138 return 139 140 self._log.info('Server info received: %s', result.jid) 141 142 self._server_info = result 143 144 self._con.get_module('SecLabels').pass_disco(result) 145 self._con.get_module('Blocking').pass_disco(result) 146 self._con.get_module('VCardTemp').pass_disco(result) 147 self._con.get_module('Carbons').pass_disco(result) 148 self._con.get_module('HTTPUpload').pass_disco(result) 149 self._con.get_module('Register').pass_disco(result) 150 151 self._con.connect_machine(restart=True) 152 153 def _parse_transports(self, info): 154 for identity in info.identities: 155 if identity.category not in ('gateway', 'headline'): 156 continue 157 158 self._log.info('Found transport: %s %s %s', 159 info.jid, identity.category, identity.type) 160 161 jid = str(info.jid) 162 if jid not in app.transport_type: 163 app.transport_type[jid] = identity.type 164 165 if identity.type in self._con.available_transports: 166 self._con.available_transports[identity.type].append(jid) 167 else: 168 self._con.available_transports[identity.type] = [jid] 169 170 def _answer_disco_items(self, _con, stanza, _properties): 171 from_ = stanza.getFrom() 172 self._log.info('Answer disco items to %s', from_) 173 174 if self._con.get_module('AdHocCommands').command_items_query(stanza): 175 raise nbxmpp.NodeProcessed 176 177 node = stanza.getTagAttr('query', 'node') 178 if node is None: 179 result = stanza.buildReply('result') 180 self._con.connection.send(result) 181 raise nbxmpp.NodeProcessed 182 183 if node == Namespace.COMMANDS: 184 self._con.get_module('AdHocCommands').command_list_query(stanza) 185 raise nbxmpp.NodeProcessed 186 187 def _answer_disco_info(self, _con, stanza, _properties): 188 from_ = stanza.getFrom() 189 self._log.info('Answer disco info %s', from_) 190 if str(from_).startswith('echo.'): 191 # Service that echos all stanzas, ignore it 192 raise nbxmpp.NodeProcessed 193 194 if self._con.get_module('AdHocCommands').command_info_query(stanza): 195 raise nbxmpp.NodeProcessed 196 197 @as_task 198 def disco_muc(self, 199 jid, 200 request_vcard=False, 201 allow_redirect=False): 202 203 _task = yield 204 205 self._log.info('Request MUC info for %s', jid) 206 207 result = yield self._nbxmpp('MUC').request_info( 208 jid, 209 request_vcard=request_vcard, 210 allow_redirect=allow_redirect) 211 212 if is_error(result): 213 raise result 214 215 if result.redirected: 216 self._log.info('MUC info received after redirect: %s -> %s', 217 jid, result.info.jid) 218 else: 219 self._log.info('MUC info received: %s', result.info.jid) 220 221 app.storage.cache.set_last_disco_info(result.info.jid, result.info) 222 223 if result.vcard is not None: 224 avatar, avatar_sha = result.vcard.get_avatar() 225 if avatar is not None: 226 if not app.interface.avatar_exists(avatar_sha): 227 app.interface.save_avatar(avatar) 228 229 app.storage.cache.set_muc_avatar_sha(result.info.jid, 230 avatar_sha) 231 app.interface.avatar_storage.invalidate_cache(result.info.jid) 232 233 self._con.get_module('VCardAvatars').muc_disco_info_update(result.info) 234 app.nec.push_incoming_event(NetworkEvent( 235 'muc-disco-update', 236 account=self._account, 237 room_jid=result.info.jid)) 238 239 yield result 240 241 @as_task 242 def disco_contact(self, contact): 243 _task = yield 244 245 fjid = contact.get_full_jid() 246 247 result = yield self.disco_info(fjid) 248 if is_error(result): 249 raise result 250 251 self._log.info('Disco Info received: %s', fjid) 252 253 app.storage.cache.set_last_disco_info(result.jid, 254 result, 255 cache_only=True) 256 257 app.nec.push_incoming_event( 258 NetworkEvent('caps-update', 259 account=self._account, 260 fjid=fjid, 261 jid=contact.jid)) 262 263 264def get_instance(*args, **kwargs): 265 return Discovery(*args, **kwargs), 'Discovery' 266