fixed link-local messaging (broken by session-centric) and removed a ton of duplicated/unused code

This commit is contained in:
Brendan Taylor 2008-05-17 02:23:46 +00:00
parent 75ad801f62
commit 517d962221
3 changed files with 217 additions and 673 deletions

View File

@ -930,6 +930,12 @@ class ConnectionVcard:
if vcard.has_key('PHOTO'): if vcard.has_key('PHOTO'):
if not isinstance(vcard['PHOTO'], dict): if not isinstance(vcard['PHOTO'], dict):
del vcard['PHOTO'] 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['jid'] = jid
vcard['resource'] = gajim.get_resource_from_jid(fjid) vcard['resource'] = gajim.get_resource_from_jid(fjid)
return vcard return vcard
@ -1195,13 +1201,9 @@ class ConnectionVcard:
#('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
self.dispatch('VCARD', vcard) 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): 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), } # List of IDs we are waiting answers for {id: (type_of_request, data), }
self.awaiting_answers = {} self.awaiting_answers = {}
# List of IDs that will produce a timeout is answer doesn't arrive # List of IDs that will produce a timeout is answer doesn't arrive
@ -1210,43 +1212,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
# keep the jids we auto added (transports contacts) to not send the # keep the jids we auto added (transports contacts) to not send the
# SUBSCRIBED event to gui # SUBSCRIBED event to gui
self.automatically_added = [] self.automatically_added = []
# keep the latest subscribed event for each jid to prevent loop when we
# acknoledge presences
self.subscribed_events = {}
# IDs of jabber:iq:last requests
self.last_ids = []
# IDs of jabber:iq:version requests
self.version_ids = []
# ID of urn:xmpp:ping requests
self.awaiting_xmpp_ping_id = None
# keep track of sessions this connection has with other JIDs # keep track of sessions this connection has with other JIDs
self.sessions = {} self.sessions = {}
try:
idle.init()
except:
HAS_IDLE = False
def build_http_auth_answer(self, iq_obj, answer):
if answer == 'yes':
self.connection.send(iq_obj.buildReply('result'))
elif answer == 'no':
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')
if opt in ('yes', 'no'):
self.build_http_auth_answer(iq_obj, opt)
else:
id = iq_obj.getTagAttr('confirm', 'id')
method = iq_obj.getTagAttr('confirm', 'method')
url = iq_obj.getTagAttr('confirm', 'url')
msg = iq_obj.getTagData('body') # In case it's a message with a body
self.dispatch('HTTP_AUTH', (method, url, id, iq_obj, msg));
raise common.xmpp.NodeProcessed
def _FeatureNegCB(self, con, stanza, session): def _FeatureNegCB(self, con, stanza, session):
gajim.log.debug('FeatureNegCB') gajim.log.debug('FeatureNegCB')
@ -1275,6 +1243,133 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
raise common.xmpp.NodeProcessed 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
# acknowledge presences
self.subscribed_events = {}
# IDs of jabber:iq:last requests
self.last_ids = []
# IDs of jabber:iq:version requests
self.version_ids = []
# ID of urn:xmpp:ping requests
self.awaiting_xmpp_ping_id = None
try:
idle.init()
except:
HAS_IDLE = False
def build_http_auth_answer(self, iq_obj, answer):
if answer == 'yes':
self.connection.send(iq_obj.buildReply('result'))
elif answer == 'no':
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')
if opt in ('yes', 'no'):
self.build_http_auth_answer(iq_obj, opt)
else:
id = iq_obj.getTagAttr('confirm', 'id')
method = iq_obj.getTagAttr('confirm', 'method')
url = iq_obj.getTagAttr('confirm', 'url')
msg = iq_obj.getTagData('body') # In case it's a message with a body
self.dispatch('HTTP_AUTH', (method, url, id, iq_obj, msg));
raise common.xmpp.NodeProcessed
def _ErrorCB(self, con, iq_obj): def _ErrorCB(self, con, iq_obj):
gajim.log.debug('ErrorCB') gajim.log.debug('ErrorCB')
jid_from = helpers.get_full_jid_from_iq(iq_obj) 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, self.dispatch('GC_INVITATION',(frm, jid_from, reason, password,
is_continued)) 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): def _pubsubEventCB(self, con, msg):
''' Called when we receive <message/> with pubsub event. ''' ''' Called when we receive <message/> with pubsub event. '''
# TODO: Logging? (actually services where logging would be useful, should # TODO: Logging? (actually services where logging would be useful, should

View File

@ -35,6 +35,7 @@ import common.xmpp
from common import helpers from common import helpers
from common import gajim from common import gajim
from common.zeroconf import zeroconf from common.zeroconf import zeroconf
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible'] 'invisible']
# kind of events we can wait for an answer # kind of events we can wait for an answer
@ -48,173 +49,23 @@ except:
gajim.log.debug(_('Unable to load idle module')) gajim.log.debug(_('Unable to load idle module'))
HAS_IDLE = False 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): def add_sha(self, p, send_caps = True):
pass pass
def add_caps(self, p): def add_caps(self, p):
pass 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): def request_vcard(self, jid = None, is_fake_jid = False):
pass pass
def send_vcard(self, vcard): def send_vcard(self, vcard):
pass pass
class ConnectionBytestream: class ConnectionBytestream(connection_handlers.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'])
def send_socks5_info(self, file_props, fast = True, receiver = None, def send_socks5_info(self, file_props, fast = True, receiver = None,
sender = None): sender = None):
''' send iq for the present streamhosts and proxies ''' ''' send iq for the present streamhosts and proxies '''
@ -274,43 +125,6 @@ class ConnectionBytestream:
streamhost.setAttr('jid', sender) streamhost.setAttr('jid', sender)
self.connection.send(iq) 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): def send_file_request(self, file_props):
''' send iq for new FT request ''' ''' send iq for new FT request '''
if not self.connection or self.connected < 2: if not self.connection or self.connected < 2:
@ -344,69 +158,6 @@ class ConnectionBytestream:
field.addOption(common.xmpp.NS_BYTESTREAM) field.addOption(common.xmpp.NS_BYTESTREAM)
self.connection.send(iq) 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): def _bytestreamSetCB(self, con, iq_obj):
gajim.log.debug('_bytestreamSetCB') gajim.log.debug('_bytestreamSetCB')
target = unicode(iq_obj.getAttr('to')) target = unicode(iq_obj.getAttr('to'))
@ -623,20 +374,12 @@ class ConnectionBytestream:
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise common.xmpp.NodeProcessed raise common.xmpp.NodeProcessed
class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream): class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream, connection_handlers.ConnectionHandlersBase):
def __init__(self): def __init__(self):
ConnectionVcard.__init__(self) ConnectionVcard.__init__(self)
ConnectionBytestream.__init__(self) ConnectionBytestream.__init__(self)
# List of IDs we are waiting answers for {id: (type_of_request, data), } connection_handlers.ConnectionHandlersBase.__init__(self)
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 = {}
try: try:
idle.init() idle.init()
except: except:
@ -644,21 +387,24 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
def _messageCB(self, ip, con, msg): def _messageCB(self, ip, con, msg):
'''Called when we receive a message''' '''Called when we receive a message'''
gajim.log.debug('Zeroconf MessageCB')
frm = msg.getFrom()
mtype = msg.getType() mtype = msg.getType()
thread_id = msg.getThread() thread_id = msg.getThread()
tim = msg.getTimestamp()
tim = helpers.datetime_tuple(tim) if not mtype:
tim = time.localtime(timegm(tim)) mtype = 'normal'
frm = msg.getFrom()
if frm == None: if frm == None:
for key in self.connection.zeroconf.contacts: for key in self.connection.zeroconf.contacts:
if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
frm = key frm = key
frm = unicode(frm)
jid = frm
session = self.get_or_create_session(frm, thread_id, mtype) frm = unicode(frm)
session = self.get_or_create_session(frm, thread_id)
if thread_id and not session.received_thread_id: if thread_id and not session.received_thread_id:
session.received_thread_id = True session.received_thread_id = True
@ -668,17 +414,17 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
if gajim.HAVE_PYCRYPTO: if gajim.HAVE_PYCRYPTO:
self._FeatureNegCB(con, msg, session) self._FeatureNegCB(con, msg, session)
return return
if msg.getTag('init') and msg.getTag('init').namespace == \ if msg.getTag('init') and msg.getTag('init').namespace == \
common.xmpp.NS_ESESSION_INIT: common.xmpp.NS_ESESSION_INIT:
self._InitE2ECB(con, msg, session) self._InitE2ECB(con, msg, session)
no_log_for = gajim.config.get_per('accounts', self.name,
'no_log_for').split()
encrypted = False 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 msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO):
if e2e_tag:
encrypted = True encrypted = True
try: try:
@ -687,43 +433,16 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
self.dispatch('FAILED_DECRYPT', (frm, tim)) self.dispatch('FAILED_DECRYPT', (frm, tim))
msgtxt = msg.getBody() msgtxt = msg.getBody()
msghtml = msg.getXHTML()
subject = msg.getSubject() # if not there, it's None 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 # invitations
invite = None invite = None
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
if not encTag: if not encTag:
invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
if invite and not invite.getTag('invite'): if invite and not invite.getTag('invite'):
invite = None 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: if encTag and self.USE_GPG:
#decrypt #decrypt
@ -733,210 +452,22 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
if keyID: if keyID:
decmsg = self.gpg.decrypt(encmsg, keyID) decmsg = self.gpg.decrypt(encmsg, keyID)
# \x00 chars are not allowed in C (so in GTK) # \x00 chars are not allowed in C (so in GTK)
decmsg = decmsg.replace('\x00', '') msgtxt = decmsg.replace('\x00', '')
if decmsg: encrypted = True
msgtxt = decmsg
encrypted = True
if mtype == 'error': if mtype == 'error':
error_msg = msg.getError() self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject)
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: else:
reply = stanza.buildReply() # XXX this shouldn't be hardcoded
reply.setType('error') if isinstance(session, ChatControlSession):
session.received(frm, msgtxt, tim, encrypted, subject, msg)
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
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)
else: else:
return self.sessions[jid][thread_id] session.received(msg)
except KeyError: # END messageCB
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)
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): def store_metacontacts(self, tags):
''' fake empty method ''' ''' fake empty method '''
# serverside metacontacts are not supported with zeroconf # serverside metacontacts are not supported with zeroconf
# (there is no server) # (there is no server)
pass pass

View File

@ -196,7 +196,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
return self.call_resolve_timeout return self.call_resolve_timeout
# callbacks called from zeroconf # callbacks called from zeroconf
def _on_new_service(self,jid): def _on_new_service(self, jid):
self.roster.setItem(jid) self.roster.setItem(jid)
self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(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)) 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='', def send_message(self, jid, msg, keyID, type = 'chat', subject='',
chatstate = None, msg_id = None, composing_xep = None, resource = None, 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 fjid = jid
if not self.connection: if not self.connection:
@ -454,13 +454,13 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
def on_send_not_ok(reason): def on_send_not_ok(reason):
reason += ' ' + _('Your message could not be sent.') 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, ret = self.connection.send(msg_iq, msg != None, on_ok=on_send_ok,
on_not_ok=on_send_not_ok) on_not_ok=on_send_not_ok)
if ret == -1: if ret == -1:
# Contact Offline # 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): def send_stanza(self, stanza):
# send a stanza untouched # send a stanza untouched