diff options
author | Brendan Taylor <bct@diffeq.com> | 2008-05-17 06:23:46 +0400 |
---|---|---|
committer | Brendan Taylor <bct@diffeq.com> | 2008-05-17 06:23:46 +0400 |
commit | 517d962221e9c133314c7074380ec6ea975978cf (patch) | |
tree | 6d604ecb4d45eb537eb5f66e9e817bf86288417b | |
parent | 75ad801f62ddce4b094dd093905a014f8d96cc42 (diff) |
fixed link-local messaging (broken by session-centric) and removed a ton of duplicated/unused code
-rw-r--r-- | src/common/connection_handlers.py | 307 | ||||
-rw-r--r-- | src/common/zeroconf/connection_handlers_zeroconf.py | 569 | ||||
-rw-r--r-- | src/common/zeroconf/connection_zeroconf.py | 8 |
3 files changed, 214 insertions, 670 deletions
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index d8bc1e868..06b2d037b 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -82,7 +82,7 @@ class ConnectionBytestream: if not file_props.has_key('stopped') or not file_props['stopped']: return False return True - + def send_success_connect_reply(self, streamhost): ''' send reply to the initiator of FT that we made a connection @@ -97,7 +97,7 @@ class ConnectionBytestream: stream_tag = query.setTag('streamhost-used') stream_tag.setAttr('jid', streamhost['jid']) self.connection.send(iq) - + def remove_transfers_for_contact(self, contact): ''' stop all active transfer for contact ''' for file_props in self.files_props.values(): @@ -112,14 +112,14 @@ class ConnectionBytestream: if contact.jid == sender_jid: file_props['error'] = -3 self.remove_transfer(file_props) - + def remove_all_transfers(self): ''' stops and removes all active connections from the socks5 pool ''' for file_props in self.files_props.values(): self.remove_transfer(file_props, remove_from_list = False) del(self.files_props) self.files_props = {} - + def remove_transfer(self, file_props, remove_from_list = True): if file_props is None: return @@ -130,7 +130,7 @@ class ConnectionBytestream: if remove_from_list: if self.files_props.has_key('sid'): del(self.files_props['sid']) - + def disconnect_transfer(self, file_props): if file_props is None: return @@ -142,7 +142,7 @@ class ConnectionBytestream: if host.has_key('idx') and host['idx'] > 0: gajim.socks5queue.remove_receiver(host['idx']) gajim.socks5queue.remove_sender(host['idx']) - + def send_socks5_info(self, file_props, fast = True, receiver = None, sender = None): ''' send iq for the present streamhosts and proxies ''' @@ -165,7 +165,7 @@ class ConnectionBytestream: if proxies.__contains__(default): proxies.remove(default) proxies.insert(0, default) - + for proxy in proxies: (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name) if host is None: @@ -313,7 +313,7 @@ class ConnectionBytestream: field.setAttr('type', 'list-single') field.addOption(common.xmpp.NS_BYTESTREAM) self.connection.send(iq) - + def _result_socks5_sid(self, sid, hash_id): ''' store the result of sha message from auth. ''' if not self.files_props.has_key(sid): @@ -321,7 +321,7 @@ class ConnectionBytestream: file_props = self.files_props[sid] file_props['hash'] = hash_id return - + def _connect_error(self, to, _id, sid, code = 404): ''' cb, when there is an error establishing BS connection, or when connection is rejected''' @@ -360,7 +360,7 @@ class ConnectionBytestream: activate.setData(file_props['proxy_receiver']) iq.setID(auth_id) self.connection.send(iq) - + # register xmpppy handlers for bytestream and FT stanzas def _bytestreamErrorCB(self, con, iq_obj): gajim.log.debug('_bytestreamErrorCB') @@ -376,7 +376,7 @@ class ConnectionBytestream: file_props['error'] = -4 self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) raise common.xmpp.NodeProcessed - + def _bytestreamSetCB(self, con, iq_obj): gajim.log.debug('_bytestreamSetCB') target = unicode(iq_obj.getAttr('to')) @@ -440,14 +440,14 @@ class ConnectionBytestream: if host['initiator'] == frm and host.has_key('idx'): gajim.socks5queue.activate_proxy(host['idx']) raise common.xmpp.NodeProcessed - + def _bytestreamResultCB(self, con, iq_obj): gajim.log.debug('_bytestreamResultCB') frm = helpers.get_full_jid_from_iq(iq_obj) real_id = unicode(iq_obj.getAttr('id')) query = iq_obj.getTag('query') gajim.proxy65_manager.resolve_result(frm, query) - + try: streamhost = query.getTag('streamhost-used') except: # this bytestream result is not what we need @@ -512,9 +512,9 @@ class ConnectionBytestream: if len(fasts) > 0: self._connect_error(frm, fasts[0]['id'], file_props['sid'], code = 406) - + raise common.xmpp.NodeProcessed - + def _siResultCB(self, con, iq_obj): gajim.log.debug('_siResultCB') id = iq_obj.getAttr('id') @@ -551,7 +551,7 @@ class ConnectionBytestream: return self.send_socks5_info(file_props, fast = True) raise common.xmpp.NodeProcessed - + def _siSetCB(self, con, iq_obj): gajim.log.debug('_siSetCB') jid = helpers.get_jid_from_iq(iq_obj) @@ -616,7 +616,7 @@ class ConnectionDisco: For identity: category, type is mandatory, name is optional. For feature: var is mandatory''' self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix) - + def request_register_agent_info(self, agent): if not self.connection: return None @@ -642,8 +642,8 @@ class ConnectionDisco: else: # fixed: blocking common.xmpp.features_nb.register(self.connection, agent, info, None) - - + + def _discover(self, ns, jid, node = None, id_prefix = None): if not self.connection: return @@ -654,11 +654,11 @@ class ConnectionDisco: if node: iq.setQuerynode(node) self.connection.send(iq) - + def _ReceivedRegInfo(self, con, resp, agent): common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) self._IqCB(con, resp) - + def _discoGetCB(self, con, iq_obj): ''' get disco info ''' frm = helpers.get_full_jid_from_iq(iq_obj) @@ -675,10 +675,10 @@ class ConnectionDisco: feature = common.xmpp.Node('feature') feature.setAttr('var', f) query.addChild(node=feature) - + self.connection.send(iq) raise common.xmpp.NodeProcessed - + def _DiscoverItemsErrorCB(self, con, iq_obj): gajim.log.debug('DiscoverItemsErrorCB') jid = helpers.get_full_jid_from_iq(iq_obj) @@ -710,7 +710,7 @@ class ConnectionDisco: continue items.append(attr) jid = helpers.get_full_jid_from_iq(iq_obj) - hostname = gajim.config.get_per('accounts', self.name, + hostname = gajim.config.get_per('accounts', self.name, 'hostname') id = iq_obj.getID() if jid == hostname and id[0] == 'p': @@ -739,7 +739,7 @@ class ConnectionDisco: if self.commandInfoQuery(con, iq_obj): raise common.xmpp.NodeProcessed - + else: iq = iq_obj.buildReply('result') q = iq.getTag('query') @@ -851,7 +851,7 @@ class ConnectionVcard: if send_caps: return self.add_caps(p) return p - + def add_caps(self, p): ''' advertise our capabilities in presence stanza (xep-0115)''' c = p.setTag('c', namespace = common.xmpp.NS_CAPS) @@ -859,7 +859,7 @@ class ConnectionVcard: c.setAttr('node', 'http://gajim.org') c.setAttr('ver', gajim.caps_hash) return p - + def node_to_dict(self, node): dict = {} for info in node.getChildren(): @@ -902,7 +902,7 @@ class ConnectionVcard: fil.close() except IOError, e: self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - + def get_cached_vcard(self, fjid, is_fake_jid = False): '''return the vcard as a dict return {} if vcard was too old @@ -930,6 +930,12 @@ class ConnectionVcard: if vcard.has_key('PHOTO'): if not isinstance(vcard['PHOTO'], dict): del vcard['PHOTO'] + elif vcard['PHOTO'].has_key('SHA'): + cached_sha = vcard['PHOTO']['SHA'] + if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \ + cached_sha: + # user change his vcard so don't use the cached one + return {} vcard['jid'] = jid vcard['resource'] = gajim.get_resource_from_jid(fjid) return vcard @@ -1195,13 +1201,9 @@ class ConnectionVcard: #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) self.dispatch('VCARD', vcard) -class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps): +# basic connection handlers used here and in zeroconf +class ConnectionHandlersBase: def __init__(self): - ConnectionVcard.__init__(self) - ConnectionBytestream.__init__(self) - ConnectionCommands.__init__(self) - ConnectionPubSub.__init__(self) - self.gmail_url = None # List of IDs we are waiting answers for {id: (type_of_request, data), } self.awaiting_answers = {} # List of IDs that will produce a timeout is answer doesn't arrive @@ -1210,8 +1212,130 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, # keep the jids we auto added (transports contacts) to not send the # SUBSCRIBED event to gui self.automatically_added = [] + + # keep track of sessions this connection has with other JIDs + self.sessions = {} + + def _FeatureNegCB(self, con, stanza, session): + gajim.log.debug('FeatureNegCB') + feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) + form = common.xmpp.DataForm(node=feature.getTag('x')) + + if form['FORM_TYPE'] == 'urn:xmpp:ssn': + self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) + else: + reply = stanza.buildReply() + reply.setType('error') + + reply.addChild(feature) + reply.addChild(node=common.xmpp.ErrorNode('service-unavailable', typ='cancel')) + + con.send(reply) + + raise common.xmpp.NodeProcessed + + def _InitE2ECB(self, con, stanza, session): + gajim.log.debug('InitE2ECB') + init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) + form = common.xmpp.DataForm(node=init.getTag('x')) + + self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) + + raise common.xmpp.NodeProcessed + + def get_or_create_session(self, jid, thread_id): + '''returns an existing session between this connection and 'jid', returns a new one if none exist.''' + + pm = True + if not gajim.interface.is_pm_contact(jid, self.name): + pm = False + jid = gajim.get_jid_without_resource(jid) + + session = self.find_session(jid, thread_id) + + if session: + return session + + if pm: + return self.make_new_session(jid, thread_id, type = 'pm') + else: + return self.make_new_session(jid, thread_id) + + def find_session(self, jid, thread_id): + try: + if not thread_id: + return self.find_null_session(jid) + else: + return self.sessions[jid][thread_id] + except KeyError: + return None + + def terminate_sessions(self): + '''send termination messages and delete all active sessions''' + for jid in self.sessions: + for thread_id in self.sessions[jid]: + self.sessions[jid][thread_id].terminate() + + self.sessions = {} + + def delete_session(self, jid, thread_id): + try: + del self.sessions[jid][thread_id] + + if not self.sessions[jid]: + del self.sessions[jid] + except KeyError: + pass + + def find_null_session(self, jid): + '''finds all of the sessions between us and a remote jid in which we +haven't received a thread_id yet and returns the session that we last +sent a message to.''' + + sessions = self.sessions[jid].values() + + # sessions that we haven't received a thread ID in + idless = filter(lambda s: not s.received_thread_id, sessions) + + # filter out everything exceptthe default session type + chat_sessions = filter(lambda s: isinstance(s, ChatControlSession), idless) + + if chat_sessions: + # return the session that we last sent a message in + chat_sessions.sort(key=lambda s: s.last_send) + return chat_sessions[-1] + else: + return None + + def make_new_session(self, jid, thread_id=None, type='chat', klass=None): + if not klass: + klass = ChatControlSession + + # determine if this session is a pm session + # if not, discard the resource + if not type == 'pm': + jid = gajim.get_jid_without_resource(jid) + + sess = klass(self, common.xmpp.JID(jid), thread_id, type) + + if not jid in self.sessions: + self.sessions[jid] = {} + + self.sessions[jid][sess.thread_id] = sess + + return sess + +class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase): + def __init__(self): + ConnectionVcard.__init__(self) + ConnectionBytestream.__init__(self) + ConnectionCommands.__init__(self) + ConnectionPubSub.__init__(self) + ConnectionHandlersBase.__init__(self) + self.gmail_url = None + # keep the latest subscribed event for each jid to prevent loop when we - # acknoledge presences + # acknowledge presences self.subscribed_events = {} # IDs of jabber:iq:last requests self.last_ids = [] @@ -1220,8 +1344,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, # ID of urn:xmpp:ping requests self.awaiting_xmpp_ping_id = None - # keep track of sessions this connection has with other JIDs - self.sessions = {} try: idle.init() except: @@ -1234,7 +1356,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, err = common.xmpp.Error(iq_obj, common.xmpp.protocol.ERR_NOT_AUTHORIZED) self.connection.send(err) - + def _HttpAuthCB(self, con, iq_obj): gajim.log.debug('HttpAuthCB') opt = gajim.config.get_per('accounts', self.name, 'http_auth') @@ -1248,33 +1370,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, self.dispatch('HTTP_AUTH', (method, url, id, iq_obj, msg)); raise common.xmpp.NodeProcessed - def _FeatureNegCB(self, con, stanza, session): - gajim.log.debug('FeatureNegCB') - feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) - form = common.xmpp.DataForm(node=feature.getTag('x')) - - if form['FORM_TYPE'] == 'urn:xmpp:ssn': - self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) - else: - reply = stanza.buildReply() - reply.setType('error') - - reply.addChild(feature) - reply.addChild(node=common.xmpp.ErrorNode('service-unavailable', typ='cancel')) - - con.send(reply) - - raise common.xmpp.NodeProcessed - - def _InitE2ECB(self, con, stanza, session): - gajim.log.debug('InitE2ECB') - init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) - form = common.xmpp.DataForm(node=init.getTag('x')) - - self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) - - raise common.xmpp.NodeProcessed - def _ErrorCB(self, con, iq_obj): gajim.log.debug('ErrorCB') jid_from = helpers.get_full_jid_from_iq(iq_obj) @@ -1727,88 +1822,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, self.dispatch('GC_INVITATION',(frm, jid_from, reason, password, is_continued)) - def get_or_create_session(self, jid, thread_id): - '''returns an existing session between this connection and 'jid', returns a new one if none exist.''' - - pm = True - if not gajim.interface.is_pm_contact(jid, self.name): - pm = False - jid = gajim.get_jid_without_resource(jid) - - session = self.find_session(jid, thread_id) - - if session: - return session - - if pm: - return self.make_new_session(jid, thread_id, type = 'pm') - else: - return self.make_new_session(jid, thread_id) - - def find_session(self, jid, thread_id): - try: - if not thread_id: - return self.find_null_session(jid) - else: - return self.sessions[jid][thread_id] - except KeyError: - return None - - def terminate_sessions(self): - '''send termination messages and delete all active sessions''' - for jid in self.sessions: - for thread_id in self.sessions[jid]: - self.sessions[jid][thread_id].terminate() - - self.sessions = {} - - def delete_session(self, jid, thread_id): - try: - del self.sessions[jid][thread_id] - - if not self.sessions[jid]: - del self.sessions[jid] - except KeyError: - pass - - def find_null_session(self, jid): - '''finds all of the sessions between us and a remote jid in which we -haven't received a thread_id yet and returns the session that we last -sent a message to.''' - - sessions = self.sessions[jid].values() - - # sessions that we haven't received a thread ID in - idless = filter(lambda s: not s.received_thread_id, sessions) - - # filter out everything exceptthe default session type - chat_sessions = filter(lambda s: isinstance(s, ChatControlSession), idless) - - if chat_sessions: - # return the session that we last sent a message in - chat_sessions.sort(key=lambda s: s.last_send) - return chat_sessions[-1] - else: - return None - - def make_new_session(self, jid, thread_id=None, type='chat', klass=None): - if not klass: - klass = ChatControlSession - - # determine if this session is a pm session - # if not, discard the resource - if not type == 'pm': - jid = gajim.get_jid_without_resource(jid) - - sess = klass(self, common.xmpp.JID(jid), thread_id, type) - - if not jid in self.sessions: - self.sessions[jid] = {} - - self.sessions[jid][sess.thread_id] = sess - - return sess - def _pubsubEventCB(self, con, msg): ''' Called when we receive <message/> with pubsub event. ''' # TODO: Logging? (actually services where logging would be useful, should diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py index 2aa945686..3befa52a3 100644 --- a/src/common/zeroconf/connection_handlers_zeroconf.py +++ b/src/common/zeroconf/connection_handlers_zeroconf.py @@ -35,6 +35,7 @@ import common.xmpp from common import helpers from common import gajim from common.zeroconf import zeroconf + STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', 'invisible'] # kind of events we can wait for an answer @@ -48,173 +49,23 @@ except: gajim.log.debug(_('Unable to load idle module')) HAS_IDLE = False -from common.stanza_session import EncryptedStanzaSession +from common import connection_handlers +from session import ChatControlSession -class ConnectionVcard: - def __init__(self): - self.vcard_sha = None - self.vcard_shas = {} # sha of contacts - self.room_jids = [] # list of gc jids so that vcard are saved in a folder +class ConnectionVcard(connection_handlers.ConnectionVcard): + def add_sha(self, p, send_caps = True): + pass - def add_sha(self, p, send_caps = True): - pass - def add_caps(self, p): pass - def node_to_dict(self, node): - dict = {} - - for info in node.getChildren(): - name = info.getName() - if name in ('ADR', 'TEL', 'EMAIL'): # we can have several - if not dict.has_key(name): - dict[name] = [] - entry = {} - for c in info.getChildren(): - entry[c.getName()] = c.getData() - dict[name].append(entry) - elif info.getChildren() == []: - dict[name] = info.getData() - else: - dict[name] = {} - for c in info.getChildren(): - dict[name][c.getName()] = c.getData() - - return dict - - def save_vcard_to_hd(self, full_jid, card): - jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) - puny_jid = helpers.sanitize_filename(jid) - path = os.path.join(gajim.VCARD_PATH, puny_jid) - if jid in self.room_jids or os.path.isdir(path): - # remove room_jid file if needed - if os.path.isfile(path): - os.remove(path) - # create folder if needed - if not os.path.isdir(path): - os.mkdir(path, 0700) - puny_nick = helpers.sanitize_filename(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = path - fil = open(path_to_file, 'w') - fil.write(str(card)) - fil.close() - - def get_cached_vcard(self, fjid, is_fake_jid = False): - '''return the vcard as a dict - return {} if vcard was too old - return None if we don't have cached vcard''' - jid, nick = gajim.get_room_and_nick_from_fjid(fjid) - puny_jid = helpers.sanitize_filename(jid) - if is_fake_jid: - puny_nick = helpers.sanitize_filename(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) - if not os.path.isfile(path_to_file): - return None - # We have the vcard cached - f = open(path_to_file) - c = f.read() - f.close() - card = common.xmpp.Node(node = c) - vcard = self.node_to_dict(card) - if vcard.has_key('PHOTO'): - if not isinstance(vcard['PHOTO'], dict): - del vcard['PHOTO'] - elif vcard['PHOTO'].has_key('SHA'): - cached_sha = vcard['PHOTO']['SHA'] - if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \ - cached_sha: - # user change his vcard so don't use the cached one - return {} - vcard['jid'] = jid - vcard['resource'] = gajim.get_resource_from_jid(fjid) - return vcard - def request_vcard(self, jid = None, is_fake_jid = False): pass - + def send_vcard(self, vcard): pass -class ConnectionBytestream: - def __init__(self): - self.files_props = {} - - def is_transfer_stopped(self, file_props): - if file_props.has_key('error') and file_props['error'] != 0: - return True - if file_props.has_key('completed') and file_props['completed']: - return True - if file_props.has_key('connected') and file_props['connected'] == False: - return True - if not file_props.has_key('stopped') or not file_props['stopped']: - return False - return True - - def send_success_connect_reply(self, streamhost): - ''' send reply to the initiator of FT that we - made a connection - ''' - if streamhost is None: - return None - iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result', - frm = streamhost['target']) - iq.setAttr('id', streamhost['id']) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - stream_tag = query.setTag('streamhost-used') - stream_tag.setAttr('jid', streamhost['jid']) - self.connection.send(iq) - - def remove_transfers_for_contact(self, contact): - ''' stop all active transfer for contact ''' - for file_props in self.files_props.values(): - if self.is_transfer_stopped(file_props): - continue - receiver_jid = unicode(file_props['receiver']).split('/')[0] - if contact.jid == receiver_jid: - file_props['error'] = -5 - self.remove_transfer(file_props) - self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, '')) - sender_jid = unicode(file_props['sender']) - if contact.jid == sender_jid: - file_props['error'] = -3 - self.remove_transfer(file_props) - - def remove_all_transfers(self): - ''' stops and removes all active connections from the socks5 pool ''' - for file_props in self.files_props.values(): - self.remove_transfer(file_props, remove_from_list = False) - del(self.files_props) - self.files_props = {} - - def remove_transfer(self, file_props, remove_from_list = True): - if file_props is None: - return - self.disconnect_transfer(file_props) - sid = file_props['sid'] - gajim.socks5queue.remove_file_props(self.name, sid) - - if remove_from_list: - if self.files_props.has_key('sid'): - del(self.files_props['sid']) - - def disconnect_transfer(self, file_props): - if file_props is None: - return - if file_props.has_key('hash'): - gajim.socks5queue.remove_sender(file_props['hash']) - - if file_props.has_key('streamhosts'): - for host in file_props['streamhosts']: - if host.has_key('idx') and host['idx'] > 0: - gajim.socks5queue.remove_receiver(host['idx']) - gajim.socks5queue.remove_sender(host['idx']) - +class ConnectionBytestream(connection_handlers.ConnectionBytestream): def send_socks5_info(self, file_props, fast = True, receiver = None, sender = None): ''' send iq for the present streamhosts and proxies ''' @@ -274,43 +125,6 @@ class ConnectionBytestream: streamhost.setAttr('jid', sender) self.connection.send(iq) - def send_file_rejection(self, file_props): - ''' informs sender that we refuse to download the file ''' - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Protocol(name = 'iq', - to = unicode(file_props['sender']), typ = 'error') - iq.setAttr('id', file_props['request-id']) - err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name = - 'forbidden', text = 'Offer Declined') - iq.addChild(node=err) - self.connection.send(iq) - - def send_file_approval(self, file_props): - ''' send iq, confirming that we want to download the file ''' - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Protocol(name = 'iq', - to = unicode(file_props['sender']), typ = 'result') - iq.setAttr('id', file_props['request-id']) - si = iq.setTag('si') - si.setNamespace(common.xmpp.NS_SI) - if file_props.has_key('offset') and file_props['offset']: - file_tag = si.setTag('file') - file_tag.setNamespace(common.xmpp.NS_FILE) - range_tag = file_tag.setTag('range') - range_tag.setAttr('offset', file_props['offset']) - feature = si.setTag('feature') - feature.setNamespace(common.xmpp.NS_FEATURE) - _feature = common.xmpp.DataForm(typ='submit') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.delAttr('type') - field.setValue(common.xmpp.NS_BYTESTREAM) - self.connection.send(iq) - def send_file_request(self, file_props): ''' send iq for new FT request ''' if not self.connection or self.connected < 2: @@ -318,7 +132,7 @@ class ConnectionBytestream: our_jid = gajim.get_jid_from_account(self.name) frm = our_jid file_props['sender'] = frm - fjid = file_props['receiver'].jid + fjid = file_props['receiver'].jid iq = common.xmpp.Protocol(name = 'iq', to = fjid, typ = 'set') iq.setID(file_props['sid']) @@ -343,70 +157,7 @@ class ConnectionBytestream: field.setAttr('type', 'list-single') field.addOption(common.xmpp.NS_BYTESTREAM) self.connection.send(iq) - - def _result_socks5_sid(self, sid, hash_id): - ''' store the result of sha message from auth. ''' - if not self.files_props.has_key(sid): - return - file_props = self.files_props[sid] - file_props['hash'] = hash_id - return - - def _connect_error(self, to, _id, sid, code = 404): - ''' cb, when there is an error establishing BS connection, or - when connection is rejected''' - msg_dict = { - 404: 'Could not connect to given hosts', - 405: 'Cancel', - 406: 'Not acceptable', - } - msg = msg_dict[code] - iq = None - iq = common.xmpp.Protocol(name = 'iq', to = to, - typ = 'error') - iq.setAttr('id', _id) - err = iq.setTag('error') - err.setAttr('code', unicode(code)) - err.setData(msg) - self.connection.send(iq) - if code == 404: - file_props = gajim.socks5queue.get_file_props(self.name, sid) - if file_props is not None: - self.disconnect_transfer(file_props) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg)) - - def _proxy_auth_ok(self, proxy): - '''cb, called after authentication to proxy server ''' - file_props = self.files_props[proxy['sid']] - iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'], - typ = 'set') - auth_id = "au_" + proxy['sid'] - iq.setID(auth_id) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('sid', proxy['sid']) - activate = query.setTag('activate') - activate.setData(file_props['proxy_receiver']) - iq.setID(auth_id) - self.connection.send(iq) - - # register xmpppy handlers for bytestream and FT stanzas - def _bytestreamErrorCB(self, con, iq_obj): - gajim.log.debug('_bytestreamErrorCB') - id = unicode(iq_obj.getAttr('id')) - frm = unicode(iq_obj.getFrom()) - query = iq_obj.getTag('query') - gajim.proxy65_manager.error_cb(frm, query) - jid = unicode(iq_obj.getFrom()) - id = id[3:] - if not self.files_props.has_key(id): - return - file_props = self.files_props[id] - file_props['error'] = -4 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) - raise common.xmpp.NodeProcessed - + def _bytestreamSetCB(self, con, iq_obj): gajim.log.debug('_bytestreamSetCB') target = unicode(iq_obj.getAttr('to')) @@ -432,7 +183,7 @@ class ConnectionBytestream: if self.files_props.has_key(sid): file_props = self.files_props[sid] file_props['fast'] = streamhosts - if file_props['type'] == 's': + if file_props['type'] == 's': if file_props.has_key('streamhosts'): file_props['streamhosts'].extend(streamhosts) else: @@ -465,14 +216,14 @@ class ConnectionBytestream: if host['initiator'] == frm and host.has_key('idx'): gajim.socks5queue.activate_proxy(host['idx']) raise common.xmpp.NodeProcessed - + def _bytestreamResultCB(self, con, iq_obj): gajim.log.debug('_bytestreamResultCB') frm = unicode(iq_obj.getFrom()) real_id = unicode(iq_obj.getAttr('id')) query = iq_obj.getTag('query') gajim.proxy65_manager.resolve_result(frm, query) - + try: streamhost = query.getTag('streamhost-used') except: # this bytestream result is not what we need @@ -531,9 +282,9 @@ class ConnectionBytestream: if len(fasts) > 0: self._connect_error(frm, fasts[0]['id'], file_props['sid'], code = 406) - + raise common.xmpp.NodeProcessed - + def _siResultCB(self, con, iq_obj): gajim.log.debug('_siResultCB') self.peerhost = con._owner.Connection._sock.getsockname() @@ -571,7 +322,7 @@ class ConnectionBytestream: return self.send_socks5_info(file_props, fast = True) raise common.xmpp.NodeProcessed - + def _siSetCB(self, con, iq_obj): gajim.log.debug('_siSetCB') jid = unicode(iq_obj.getFrom()) @@ -595,7 +346,7 @@ class ConnectionBytestream: if mime_type is not None: file_props['mime-type'] = mime_type our_jid = gajim.get_jid_from_account(self.name) - file_props['receiver'] = our_jid + file_props['receiver'] = our_jid file_props['sender'] = unicode(iq_obj.getFrom()) file_props['request-id'] = unicode(iq_obj.getAttr('id')) file_props['sid'] = unicode(si.getAttr('id')) @@ -623,320 +374,100 @@ class ConnectionBytestream: self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) raise common.xmpp.NodeProcessed -class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream): +class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream, connection_handlers.ConnectionHandlersBase): def __init__(self): ConnectionVcard.__init__(self) ConnectionBytestream.__init__(self) - # List of IDs we are waiting answers for {id: (type_of_request, data), } - self.awaiting_answers = {} - # List of IDs that will produce a timeout is answer doesn't arrive - # {time_of_the_timeout: (id, message to send to gui), } - self.awaiting_timeouts = {} - # keep the jids we auto added (transports contacts) to not send the - # SUBSCRIBED event to gui - self.automatically_added = [] - # keep track of sessions this connection has with other JIDs - self.sessions = {} + connection_handlers.ConnectionHandlersBase.__init__(self) + try: idle.init() except: HAS_IDLE = False - + def _messageCB(self, ip, con, msg): '''Called when we receive a message''' + + gajim.log.debug('Zeroconf MessageCB') + + frm = msg.getFrom() mtype = msg.getType() thread_id = msg.getThread() - tim = msg.getTimestamp() - tim = helpers.datetime_tuple(tim) - tim = time.localtime(timegm(tim)) - frm = msg.getFrom() + + if not mtype: + mtype = 'normal' if frm == None: for key in self.connection.zeroconf.contacts: if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: frm = key + frm = unicode(frm) - jid = frm - session = self.get_or_create_session(frm, thread_id, mtype) + session = self.get_or_create_session(frm, thread_id) if thread_id and not session.received_thread_id: session.received_thread_id = True - + if msg.getTag('feature') and msg.getTag('feature').namespace == \ common.xmpp.NS_FEATURE: if gajim.HAVE_PYCRYPTO: self._FeatureNegCB(con, msg, session) return + if msg.getTag('init') and msg.getTag('init').namespace == \ common.xmpp.NS_ESESSION_INIT: self._InitE2ECB(con, msg, session) - no_log_for = gajim.config.get_per('accounts', self.name, - 'no_log_for').split() encrypted = False - chatstate = None + tim = msg.getTimestamp() + tim = helpers.datetime_tuple(tim) + tim = time.localtime(timegm(tim)) - e2e_tag = msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO) - if e2e_tag: + if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO): encrypted = True try: msg = session.decrypt_stanza(msg) except: self.dispatch('FAILED_DECRYPT', (frm, tim)) - + msgtxt = msg.getBody() - msghtml = msg.getXHTML() subject = msg.getSubject() # if not there, it's None - encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) - decmsg = '' - form_node = msg.getTag('x', namespace = common.xmpp.NS_DATA) # invitations invite = None + encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) + if not encTag: invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) if invite and not invite.getTag('invite'): invite = None - delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None - msg_id = None - composing_xep = None - xtags = msg.getTags('x') - # chatstates - look for chatstate tags in a message if not delayed - if not delayed: - composing_xep = False - children = msg.getChildren() - for child in children: - if child.getNamespace() == 'http://jabber.org/protocol/chatstates': - chatstate = child.getName() - composing_xep = 'XEP-0085' - break - # No JEP-0085 support, fallback to JEP-0022 - if not chatstate: - chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT) - if chatstate_child: - chatstate = 'active' - composing_xep = 'XEP-0022' - if not msgtxt and chatstate_child.getTag('composing'): - chatstate = 'composing' - # JEP-0172 User Nickname - user_nick = msg.getTagData('nick') - if not user_nick: - user_nick = '' if encTag and self.USE_GPG: #decrypt encmsg = encTag.getData() - + keyID = gajim.config.get_per('accounts', self.name, 'keyid') if keyID: decmsg = self.gpg.decrypt(encmsg, keyID) # \x00 chars are not allowed in C (so in GTK) - decmsg = decmsg.replace('\x00', '') - if decmsg: - msgtxt = decmsg - encrypted = True - if mtype == 'error': - error_msg = msg.getError() - if not error_msg: - error_msg = msgtxt - msgtxt = None - if self.name not in no_log_for: - gajim.logger.write('error', frm, error_msg, tim = tim, - subject = subject) - self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, - tim)) - elif mtype == 'chat': # it's type 'chat' - if not msg.getTag('body') and chatstate is None: #no <body> - return - if msg.getTag('body') and self.name not in no_log_for and jid not in\ - no_log_for and msgtxt: - msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim, - subject = subject) - self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject, - chatstate, msg_id, composing_xep, user_nick, msghtml, session, - form_node)) - elif mtype == 'normal': # it's single message - if self.name not in no_log_for and jid not in no_log_for and msgtxt: - gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim, - subject = subject) - if invite: - self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal', - subject, chatstate, msg_id, composing_xep, user_nick, msghtml, - session, form_node)) - # END messageCB - - def _FeatureNegCB(self, con, stanza, session): - gajim.log.debug('FeatureNegCB') - feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) - form = common.xmpp.DataForm(node=feature.getTag('x')) - - if form['FORM_TYPE'] == 'urn:xmpp:ssn': - self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) - else: - reply = stanza.buildReply() - reply.setType('error') + msgtxt = decmsg.replace('\x00', '') + encrypted = True - reply.addChild(feature) - reply.addChild(node=xmpp.ErrorNode('service-unavailable', typ='cancel')) - - con.send(reply) - - raise common.xmpp.NodeProcessed - - def _InitE2ECB(self, con, stanza, session): - gajim.log.debug('InitE2ECB') - init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) - form = common.xmpp.DataForm(node=init.getTag('x')) - - self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) - - raise common.xmpp.NodeProcessed - - def terminate_sessions(self): - '''send termination messages and delete all active sessions''' - # XXX - pass - - def get_or_create_session(self, jid, thread_id, type): - '''returns an existing session between this connection and 'jid', returns a new one if none exist.''' - session = self.find_session(jid, thread_id, type) - - if session: - return session + if mtype == 'error': + self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject) else: - # it's possible we initiated a session with a bare JID and this is the - # first time we've seen a resource - bare_jid = gajim.get_jid_without_resource(jid) - if bare_jid != jid: - session = self.find_session(bare_jid, thread_id, type) - if session: - if not session.received_thread_id: - thread_id = session.thread_id - - self.move_session(bare_jid, thread_id, jid.split("/")[1]) - return session - - return self.make_new_session(jid, thread_id, type) - - def find_session(self, jid, thread_id, type): - try: - if type == 'chat' and not thread_id: - return self.find_null_session(jid) + # XXX this shouldn't be hardcoded + if isinstance(session, ChatControlSession): + session.received(frm, msgtxt, tim, encrypted, subject, msg) else: - return self.sessions[jid][thread_id] - except KeyError: - return None - - def delete_session(self, jid, thread_id): - try: - del self.sessions[jid][thread_id] - - if not self.sessions[jid]: - del self.sessions[jid] - except KeyError: - print "jid %s should have been in %s, but it wasn't. missing session?" % (repr(jid), repr(self.sessions.keys())) - - def move_session(self, original_jid, thread_id, to_resource): - '''moves a session to another resource.''' - session = self.sessions[original_jid][thread_id] - - del self.sessions[original_jid][thread_id] - - new_jid = gajim.get_jid_without_resource(original_jid) + '/' + to_resource - session.jid = common.xmpp.JID(new_jid) - - if not new_jid in self.sessions: - self.sessions[new_jid] = {} - - self.sessions[new_jid][thread_id] = session - - def find_null_session(self, jid): - '''finds all of the sessions between us and jid that jid hasn't sent a thread_id in yet. - -returns the session that we last sent a message to.''' - - sessions_with_jid = self.sessions[jid].values() - no_threadid_sessions = filter(lambda s: not s.received_thread_id, sessions_with_jid) + session.received(msg) + # END messageCB - if no_threadid_sessions: - no_threadid_sessions.sort(key=lambda s: s.last_send) - return no_threadid_sessions[-1] - else: - return None - - def make_new_session(self, jid, thread_id = None, type = 'chat'): - sess = EncryptedStanzaSession(self, common.xmpp.JID(jid), thread_id, type) - - if not jid in self.sessions: - self.sessions[jid] = {} - - self.sessions[jid][sess.thread_id] = sess - - return sess - - def parse_data_form(self, node): - dic = {} - tag = node.getTag('title') - if tag: - dic['title'] = tag.getData() - tag = node.getTag('instructions') - if tag: - dic['instructions'] = tag.getData() - i = 0 - for child in node.getChildren(): - if child.getName() != 'field': - continue - var = child.getAttr('var') - ctype = child.getAttr('type') - label = child.getAttr('label') - if not var and ctype != 'fixed': # We must have var if type != fixed - continue - dic[i] = {} - if var: - dic[i]['var'] = var - if ctype: - dic[i]['type'] = ctype - if label: - dic[i]['label'] = label - tags = child.getTags('value') - if len(tags): - dic[i]['values'] = [] - for tag in tags: - data = tag.getData() - if ctype == 'boolean': - if data in ('yes', 'true', 'assent', '1'): - data = True - else: - data = False - dic[i]['values'].append(data) - tag = child.getTag('desc') - if tag: - dic[i]['desc'] = tag.getData() - option_tags = child.getTags('option') - if len(option_tags): - dic[i]['options'] = {} - j = 0 - for option_tag in option_tags: - dic[i]['options'][j] = {} - label = option_tag.getAttr('label') - tags = option_tag.getTags('value') - dic[i]['options'][j]['values'] = [] - for tag in tags: - dic[i]['options'][j]['values'].append(tag.getData()) - if not label: - label = dic[i]['options'][j]['values'][0] - dic[i]['options'][j]['label'] = label - j += 1 - if not dic[i].has_key('values'): - dic[i]['values'] = [dic[i]['options'][0]['values'][0]] - i += 1 - return dic - def store_metacontacts(self, tags): ''' fake empty method ''' - # serverside metacontacts are not supported with zeroconf + # serverside metacontacts are not supported with zeroconf # (there is no server) pass diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index f57338403..d306d199b 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -196,7 +196,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): return self.call_resolve_timeout # callbacks called from zeroconf - def _on_new_service(self,jid): + def _on_new_service(self, jid): self.roster.setItem(jid) self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid))) self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0, None)) @@ -362,7 +362,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): def send_message(self, jid, msg, keyID, type = 'chat', subject='', chatstate = None, msg_id = None, composing_xep = None, resource = None, - user_nick = None, session=None): + user_nick = None, session=None, forward_from=None, form_node=None, original_message=None): fjid = jid if not self.connection: @@ -454,13 +454,13 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): def on_send_not_ok(reason): reason += ' ' + _('Your message could not be sent.') - self.dispatch('MSGERROR', [jid, '-1', reason, None, None]) + self.dispatch('MSGERROR', [jid, '-1', reason, None, None, session]) ret = self.connection.send(msg_iq, msg != None, on_ok=on_send_ok, on_not_ok=on_send_not_ok) if ret == -1: # Contact Offline - self.dispatch('MSGERROR', [jid, '-1', _('Contact is offline. Your message could not be sent.'), None, None]) + self.dispatch('MSGERROR', [jid, '-1', _('Contact is offline. Your message could not be sent.'), None, None, session]) def send_stanza(self, stanza): # send a stanza untouched |