merge jingleFT branch! Thanks to Zhenchao Li and Jefry Lagrange for their work.
This commit is contained in:
commit
6d178205fd
|
@ -53,7 +53,7 @@ from common.logger import constants
|
|||
from common.pep import MOODS, ACTIVITIES
|
||||
from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||
from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
|
||||
from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP
|
||||
from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER
|
||||
from common.xmpp.protocol import NS_CHATSTATES
|
||||
from common.connection_handlers_events import MessageOutgoingEvent
|
||||
from common.exceptions import GajimGeneralException
|
||||
|
@ -1722,13 +1722,13 @@ class ChatControl(ChatControlBase):
|
|||
self._video_button.set_sensitive(self.video_available)
|
||||
|
||||
# Send file
|
||||
if self.contact.supports(NS_FILE) and (self.type_id == 'chat' or \
|
||||
if (self.contact.supports(NS_FILE) or self.contact.supports(NS_JINGLE_FILE_TRANSFER)) and (self.type_id == 'chat' or \
|
||||
self.gc_contact.resource):
|
||||
self._send_file_button.set_sensitive(True)
|
||||
self._send_file_button.set_tooltip_text('')
|
||||
else:
|
||||
self._send_file_button.set_sensitive(False)
|
||||
if not self.contact.supports(NS_FILE):
|
||||
if not (self.contact.supports(NS_FILE) or self.contact.supports(NS_JINGLE_FILE_TRANSFER)):
|
||||
self._send_file_button.set_tooltip_text(_(
|
||||
"This contact does not support file transfer."))
|
||||
else:
|
||||
|
|
|
@ -38,10 +38,10 @@ import logging
|
|||
log = logging.getLogger('gajim.c.caps_cache')
|
||||
|
||||
from common.xmpp import (NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES,
|
||||
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS)
|
||||
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS, NS_JINGLE_FILE_TRANSFER)
|
||||
# Features where we cannot safely assume that the other side supports them
|
||||
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION,
|
||||
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO]
|
||||
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_FILE_TRANSFER]
|
||||
|
||||
# Query entry status codes
|
||||
NEW = 0
|
||||
|
|
|
@ -30,6 +30,7 @@ import stat
|
|||
|
||||
from common import gajim
|
||||
import logger
|
||||
from common import jingle_xtls
|
||||
|
||||
# DO NOT MOVE ABOVE OF import gajim
|
||||
import sqlite3 as sqlite
|
||||
|
@ -266,6 +267,8 @@ def check_and_possibly_create_paths():
|
|||
MY_DATA = configpaths.gajimpaths['MY_DATA']
|
||||
MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
|
||||
MY_CACHE = configpaths.gajimpaths['MY_CACHE']
|
||||
XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS']
|
||||
LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT']
|
||||
|
||||
PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
|
||||
|
||||
|
@ -342,6 +345,17 @@ def check_and_possibly_create_paths():
|
|||
print _('%s is a directory but should be a file') % CACHE_DB_PATH
|
||||
print _('Gajim will now exit')
|
||||
sys.exit()
|
||||
|
||||
if not os.path.exists(XTLS_CERTS):
|
||||
create_path(XTLS_CERTS)
|
||||
if not os.path.exists(LOCAL_XTLS_CERTS):
|
||||
create_path(LOCAL_XTLS_CERTS)
|
||||
cert_name = os.path.join(LOCAL_XTLS_CERTS,
|
||||
jingle_xtls.SELF_SIGNED_CERTIFICATE)
|
||||
if not (os.path.exists(cert_name + '.cert') and os.path.exists(
|
||||
cert_name + '.pkey')):
|
||||
jingle_xtls.make_certs(cert_name, 'gajim')
|
||||
|
||||
|
||||
def create_path(directory):
|
||||
head, tail = os.path.split(directory)
|
||||
|
|
|
@ -141,7 +141,7 @@ class ConfigPaths:
|
|||
d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
|
||||
'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets',
|
||||
'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities',
|
||||
'PLUGINS_USER': u'plugins'}
|
||||
'PLUGINS_USER': u'plugins', 'MY_PEER_CERTS': u'certs'}
|
||||
for name in d:
|
||||
self.add(name, TYPE_DATA, windowsify(d[name]))
|
||||
|
||||
|
@ -151,6 +151,7 @@ class ConfigPaths:
|
|||
self.add(name, TYPE_CACHE, windowsify(d[name]))
|
||||
|
||||
self.add('MY_CONFIG', TYPE_CONFIG, '')
|
||||
self.add('MY_CERT', TYPE_CONFIG, '')
|
||||
|
||||
basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir))
|
||||
self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
|
||||
|
|
|
@ -45,6 +45,7 @@ from common import helpers
|
|||
from common import gajim
|
||||
from common import exceptions
|
||||
from common import dataforms
|
||||
from common import jingle_xtls
|
||||
from common.commands import ConnectionCommands
|
||||
from common.pubsub import ConnectionPubSub
|
||||
from common.pep import ConnectionPEP
|
||||
|
@ -189,7 +190,10 @@ class ConnectionDisco:
|
|||
query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[
|
||||
0])
|
||||
for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI,
|
||||
common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS):
|
||||
common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS,
|
||||
common.xmpp.NS_JINGLE_FILE_TRANSFER, common.xmpp.NS_JINGLE_XTLS,
|
||||
common.xmpp.NS_PUBKEY_PUBKEY, common.xmpp.NS_PUBKEY_REVOKE,
|
||||
common.xmpp.NS_PUBKEY_ATTEST):
|
||||
feature = common.xmpp.Node('feature')
|
||||
feature.setAttr('var', f)
|
||||
query.addChild(node=feature)
|
||||
|
@ -1979,10 +1983,37 @@ ConnectionJingle, ConnectionIBBytestream):
|
|||
gajim.nec.push_incoming_event(SearchFormReceivedEvent(None,
|
||||
conn=self, stanza=iq_obj))
|
||||
|
||||
def _StreamCB(self, con, iq_obj):
|
||||
log.debug('StreamCB')
|
||||
gajim.nec.push_incoming_event(StreamReceivedEvent(None,
|
||||
conn=self, stanza=iq_obj))
|
||||
def _search_fields_received(self, con, iq_obj):
|
||||
jid = jid = helpers.get_jid_from_iq(iq_obj)
|
||||
tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH)
|
||||
if not tag:
|
||||
self.dispatch('SEARCH_FORM', (jid, None, False))
|
||||
return
|
||||
df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
|
||||
if df:
|
||||
self.dispatch('SEARCH_FORM', (jid, df, True))
|
||||
return
|
||||
df = {}
|
||||
for i in iq_obj.getQueryPayload():
|
||||
df[i.getName()] = i.getData()
|
||||
self.dispatch('SEARCH_FORM', (jid, df, False))
|
||||
|
||||
def _PubkeyGetCB(self, con, iq_obj):
|
||||
log.info('PubkeyGetCB')
|
||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
||||
sid = iq_obj.getAttr('id')
|
||||
jingle_xtls.send_cert(con, jid_from, sid)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _PubkeyResultCB(self, con, iq_obj):
|
||||
log.info('PubkeyResultCB')
|
||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
||||
jingle_xtls.handle_new_cert(con, iq_obj, jid_from)
|
||||
|
||||
def _StreamCB(self, con, obj):
|
||||
if obj.getTag('conflict'):
|
||||
# disconnected because of a resource conflict
|
||||
self.dispatch('RESOURCE_CONFLICT', ())
|
||||
|
||||
def _register_handlers(self, con, con_type):
|
||||
# try to find another way to register handlers in each class
|
||||
|
@ -2070,5 +2101,7 @@ ConnectionJingle, ConnectionIBBytestream):
|
|||
con.RegisterHandler('iq', self._ResultCB, 'result')
|
||||
con.RegisterHandler('presence', self._StanzaArrivedCB)
|
||||
con.RegisterHandler('message', self._StanzaArrivedCB)
|
||||
con.RegisterHandler('unknown', self._StreamCB,
|
||||
common.xmpp.NS_XMPP_STREAMS, xmlns=common.xmpp.NS_STREAMS)
|
||||
con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
|
||||
con.RegisterHandler('iq', self._PubkeyGetCB, 'get', common.xmpp.NS_PUBKEY_PUBKEY)
|
||||
con.RegisterHandler('iq', self._PubkeyResultCB, 'result', common.xmpp.NS_PUBKEY_PUBKEY)
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ from common.zeroconf import zeroconf
|
|||
from common.logger import LOG_DB_PATH
|
||||
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
|
||||
from common.xmpp.protocol import NS_CHATSTATES
|
||||
from common.jingle_transport import JingleTransportSocks5
|
||||
|
||||
import gtkgui_helpers
|
||||
|
||||
|
@ -1392,6 +1393,16 @@ class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent):
|
|||
self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
|
||||
self.sid = self.jingle_session.sid
|
||||
return True
|
||||
|
||||
class JingleTransferCancelledEvent(nec.NetworkIncomingEvent):
|
||||
name = 'jingleFT-cancelled-received'
|
||||
base_network_events = []
|
||||
|
||||
def generate(self):
|
||||
self.fjid = self.jingle_session.peerjid
|
||||
self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
|
||||
self.sid = self.jingle_session.sid
|
||||
return True
|
||||
|
||||
class JingleErrorReceivedEvent(nec.NetworkIncomingEvent):
|
||||
name = 'jingle-error-received'
|
||||
|
@ -1901,6 +1912,10 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
name = 'file-request-received'
|
||||
base_network_events = []
|
||||
|
||||
def init(self):
|
||||
self.jingle_content = None
|
||||
self.FT_content = None
|
||||
|
||||
def generate(self):
|
||||
self.get_id()
|
||||
self.fjid = self.conn._ft_get_from(self.stanza)
|
||||
|
@ -1908,28 +1923,36 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
self.file_props = {'type': 'r'}
|
||||
self.file_props['sender'] = self.fjid
|
||||
self.file_props['request-id'] = self.id_
|
||||
si = self.stanza.getTag('si')
|
||||
profile = si.getAttr('profile')
|
||||
if profile != xmpp.NS_FILE:
|
||||
self.conn.send_file_rejection(self.file_props, code='400', typ='profile')
|
||||
raise xmpp.NodeProcessed
|
||||
feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE)
|
||||
if not feature_tag:
|
||||
return
|
||||
form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA)
|
||||
if not form_tag:
|
||||
return
|
||||
self.dataform = dataforms.ExtendForm(node=form_tag)
|
||||
for f in self.dataform.iter_fields():
|
||||
if f.var == 'stream-method' and f.type == 'list-single':
|
||||
values = [o[1] for o in f.options]
|
||||
self.file_props['stream-methods'] = ' '.join(values)
|
||||
if xmpp.NS_BYTESTREAM in values or xmpp.NS_IBB in values:
|
||||
break
|
||||
if self.jingle_content:
|
||||
self.file_props['session-type'] = 'jingle'
|
||||
self.file_props['stream-methods'] = xmpp.NS_BYTESTREAM
|
||||
file_tag = self.jingle_content.getTag('description').getTag(
|
||||
'offer').getTag('file')
|
||||
else:
|
||||
self.conn.send_file_rejection(self.file_props, code='400', typ='stream')
|
||||
raise xmpp.NodeProcessed
|
||||
file_tag = si.getTag('file')
|
||||
si = self.stanza.getTag('si')
|
||||
profile = si.getAttr('profile')
|
||||
if profile != xmpp.NS_FILE:
|
||||
self.conn.send_file_rejection(self.file_props, code='400',
|
||||
typ='profile')
|
||||
raise xmpp.NodeProcessed
|
||||
feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE)
|
||||
if not feature_tag:
|
||||
return
|
||||
form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA)
|
||||
if not form_tag:
|
||||
return
|
||||
self.dataform = dataforms.ExtendForm(node=form_tag)
|
||||
for f in self.dataform.iter_fields():
|
||||
if f.var == 'stream-method' and f.type == 'list-single':
|
||||
values = [o[1] for o in f.options]
|
||||
self.file_props['stream-methods'] = ' '.join(values)
|
||||
if xmpp.NS_BYTESTREAM in values or xmpp.NS_IBB in values:
|
||||
break
|
||||
else:
|
||||
self.conn.send_file_rejection(self.file_props, code='400',
|
||||
typ='stream')
|
||||
raise xmpp.NodeProcessed
|
||||
file_tag = si.getTag('file')
|
||||
for attribute in file_tag.getAttrs():
|
||||
if attribute in ('name', 'size', 'hash', 'date'):
|
||||
val = file_tag.getAttr(attribute)
|
||||
|
@ -1940,13 +1963,41 @@ class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
if file_desc_tag is not None:
|
||||
self.file_props['desc'] = file_desc_tag.getData()
|
||||
|
||||
mime_type = si.getAttr('mime-type')
|
||||
if mime_type is not None:
|
||||
self.file_props['mime-type'] = mime_type
|
||||
if not self.jingle_content:
|
||||
mime_type = si.getAttr('mime-type')
|
||||
if mime_type is not None:
|
||||
self.file_props['mime-type'] = mime_type
|
||||
|
||||
self.file_props['receiver'] = self.conn._ft_get_our_jid()
|
||||
self.file_props['sid'] = unicode(si.getAttr('id'))
|
||||
self.file_props['transfered_size'] = []
|
||||
if self.jingle_content:
|
||||
self.FT_content.use_security = bool(self.jingle_content.getTag(
|
||||
'security'))
|
||||
self.file_props['session-sid'] = unicode(self.stanza.getTag(
|
||||
'jingle').getAttr('sid'))
|
||||
|
||||
self.FT_content.file_props = self.file_props
|
||||
if not self.FT_content.transport:
|
||||
self.FT_content.transport = JingleTransportSocks5()
|
||||
self.FT_content.transport.set_our_jid(
|
||||
self.FT_content.session.ourjid)
|
||||
self.FT_content.transport.set_connection(
|
||||
self.FT_content.session.connection)
|
||||
self.file_props['sid'] = self.FT_content.transport.sid
|
||||
self.FT_content.session.connection.files_props[
|
||||
self.file_props['sid']] = self.file_props
|
||||
self.FT_content.transport.set_file_props(self.file_props)
|
||||
if self.file_props.has_key('streamhosts'):
|
||||
self.file_props['streamhosts'].extend(
|
||||
self.FT_content.transport.remote_candidates)
|
||||
else:
|
||||
self.file_props['streamhosts'] = \
|
||||
self.FT_content.transport.remote_candidates
|
||||
for host in self.file_props['streamhosts']:
|
||||
host['initiator'] = self.FT_content.session.initiator
|
||||
host['target'] = self.FT_content.session.responder
|
||||
else:
|
||||
self.file_props['sid'] = unicode(si.getAttr('id'))
|
||||
return True
|
||||
|
||||
class FileRequestErrorEvent(nec.NetworkIncomingEvent):
|
||||
|
|
|
@ -62,6 +62,7 @@ MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
|
|||
MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
|
||||
MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
|
||||
MY_CACERTS = gajimpaths['MY_CACERTS']
|
||||
MY_PEER_CERTS_PATH = gajimpaths['MY_PEER_CERTS']
|
||||
TMP = gajimpaths['TMP']
|
||||
DATA_DIR = gajimpaths['DATA']
|
||||
ICONS_DIR = gajimpaths['ICONS']
|
||||
|
@ -69,6 +70,7 @@ HOME_DIR = gajimpaths['HOME']
|
|||
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
|
||||
gajimpaths['PLUGINS_USER']]
|
||||
PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
|
||||
MY_CERT_DIR = gajimpaths['MY_CERT']
|
||||
|
||||
try:
|
||||
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
||||
|
@ -206,7 +208,9 @@ gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE,
|
|||
'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE,
|
||||
xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog',
|
||||
'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN,
|
||||
xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL]
|
||||
xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL,
|
||||
xmpp.NS_HASHES, xmpp.NS_HASHES_MD5, xmpp.NS_HASHES_SHA1,
|
||||
xmpp.NS_HASHES_SHA256, xmpp.NS_HASHES_SHA512]
|
||||
|
||||
# Optional features gajim supports per account
|
||||
gajim_optional_features = {}
|
||||
|
|
|
@ -1336,6 +1336,10 @@ def update_optional_features(account = None):
|
|||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
|
||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
|
||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
|
||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_FILE_TRANSFER)
|
||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_XTLS)
|
||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_BYTESTREAM)
|
||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_IBB)
|
||||
gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
|
||||
gajim.gajim_common_features + gajim.gajim_optional_features[a])
|
||||
# re-send presence with new hash
|
||||
|
|
|
@ -37,7 +37,11 @@ import gajim
|
|||
from jingle_session import JingleSession, JingleStates
|
||||
if gajim.HAVE_FARSTREAM:
|
||||
from jingle_rtp import JingleAudio, JingleVideo
|
||||
from jingle_ft import JingleFileTransfer
|
||||
from jingle_transport import JingleTransportSocks5, JingleTransportIBB
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('gajim.c.jingle')
|
||||
|
||||
class ConnectionJingle(object):
|
||||
"""
|
||||
|
@ -75,27 +79,38 @@ class ConnectionJingle(object):
|
|||
"""
|
||||
# get data
|
||||
jid = helpers.get_full_jid_from_iq(stanza)
|
||||
id = stanza.getID()
|
||||
id_ = stanza.getID()
|
||||
|
||||
if (jid, id) in self.__iq_responses.keys():
|
||||
self.__iq_responses[(jid, id)].on_stanza(stanza)
|
||||
del self.__iq_responses[(jid, id)]
|
||||
if (jid, id_) in self.__iq_responses.keys():
|
||||
self.__iq_responses[(jid, id_)].on_stanza(stanza)
|
||||
del self.__iq_responses[(jid, id_)]
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
jingle = stanza.getTag('jingle')
|
||||
if not jingle: return
|
||||
sid = jingle.getAttr('sid')
|
||||
# a jingle element is not necessary in iq-result stanza
|
||||
# don't check for that
|
||||
if jingle:
|
||||
sid = jingle.getAttr('sid')
|
||||
else:
|
||||
sid = None
|
||||
for sesn in self._sessions.values():
|
||||
if id_ in sesn.iq_ids:
|
||||
sesn.on_stanza(stanza)
|
||||
return
|
||||
|
||||
# do we need to create a new jingle object
|
||||
if sid not in self._sessions:
|
||||
#TODO: tie-breaking and other things...
|
||||
newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
|
||||
newjingle = JingleSession(con=self, weinitiate=False, jid=jid,
|
||||
iq_id=id_, sid=sid)
|
||||
self._sessions[sid] = newjingle
|
||||
|
||||
# we already have such session in dispatcher...
|
||||
self._sessions[sid].collect_iq_id(id_)
|
||||
self._sessions[sid].on_stanza(stanza)
|
||||
# Delete invalid/unneeded sessions
|
||||
if sid in self._sessions and self._sessions[sid].state == JingleStates.ended:
|
||||
if sid in self._sessions and \
|
||||
self._sessions[sid].state == JingleStates.ended:
|
||||
self.delete_jingle_session(sid)
|
||||
|
||||
raise xmpp.NodeProcessed
|
||||
|
@ -126,16 +141,55 @@ class ConnectionJingle(object):
|
|||
jingle.start_session()
|
||||
return jingle.sid
|
||||
|
||||
def start_file_transfer(self, jid, file_props):
|
||||
logger.info("start file transfer with file: %s" % file_props)
|
||||
contact = gajim.contacts.get_contact_with_highest_priority(self.name,
|
||||
gajim.get_jid_without_resource(jid))
|
||||
if contact is None:
|
||||
return
|
||||
use_security = contact.supports(xmpp.NS_JINGLE_XTLS)
|
||||
jingle = JingleSession(self, weinitiate=True, jid=jid)
|
||||
# this is a file transfer
|
||||
jingle.session_type_FT = True
|
||||
self._sessions[jingle.sid] = jingle
|
||||
file_props['sid'] = jingle.sid
|
||||
if contact.supports(xmpp.NS_JINGLE_BYTESTREAM):
|
||||
transport = JingleTransportSocks5()
|
||||
elif contact.supports(xmpp.NS_JINGLE_IBB):
|
||||
transport = JingleTransportIBB()
|
||||
c = JingleFileTransfer(jingle, transport=transport,
|
||||
file_props=file_props, use_security=use_security)
|
||||
jingle.hash_algo = self.__hash_support(contact)
|
||||
jingle.add_content('file' + helpers.get_random_string_16(), c)
|
||||
jingle.start_session()
|
||||
return c.transport.sid
|
||||
|
||||
def __hash_support(self, contact):
|
||||
|
||||
if contact.supports(xmpp.NS_HASHES):
|
||||
if contact.supports(xmpp.NS_HASHES_MD5):
|
||||
return 'md5'
|
||||
elif contact.supports(xmpp.NS_HASHES_SHA1):
|
||||
return 'sha-1'
|
||||
elif contact.supports(xmpp.NS_HASHES_SHA256):
|
||||
return 'sha-256'
|
||||
elif contact.supports(xmpp.NS_HASHES_SHA512):
|
||||
return 'sha-512'
|
||||
|
||||
return None
|
||||
|
||||
def iter_jingle_sessions(self, jid, sid=None, media=None):
|
||||
if sid:
|
||||
return (session for session in self._sessions.values() if session.sid == sid)
|
||||
sessions = (session for session in self._sessions.values() if session.peerjid == jid)
|
||||
return (session for session in self._sessions.values() if \
|
||||
session.sid == sid)
|
||||
sessions = (session for session in self._sessions.values() if \
|
||||
session.peerjid == jid)
|
||||
if media:
|
||||
if media not in ('audio', 'video'):
|
||||
if media not in ('audio', 'video', 'file'):
|
||||
return tuple()
|
||||
else:
|
||||
return (session for session in sessions if session.get_content(media))
|
||||
return (session for session in sessions if \
|
||||
session.get_content(media))
|
||||
else:
|
||||
return sessions
|
||||
|
||||
|
@ -147,6 +201,8 @@ class ConnectionJingle(object):
|
|||
else:
|
||||
return None
|
||||
elif media:
|
||||
if media not in ('audio', 'video', 'file'):
|
||||
return None
|
||||
for session in self._sessions.values():
|
||||
if session.peerjid == jid and session.get_content(media):
|
||||
return session
|
||||
|
|
|
@ -16,6 +16,7 @@ Handles Jingle contents (XEP 0166)
|
|||
"""
|
||||
|
||||
import xmpp
|
||||
from jingle_transport import JingleTransportIBB
|
||||
|
||||
contents = {}
|
||||
|
||||
|
@ -69,7 +70,7 @@ class JingleContent(object):
|
|||
'session-initiate': [self.__on_transport_info],
|
||||
'session-terminate': [],
|
||||
'transport-info': [self.__on_transport_info],
|
||||
'transport-replace': [],
|
||||
'transport-replace': [self.__on_transport_replace],
|
||||
'transport-accept': [],
|
||||
'transport-reject': [],
|
||||
'iq-result': [],
|
||||
|
@ -99,7 +100,7 @@ class JingleContent(object):
|
|||
"""
|
||||
Add a list of candidates to the list of remote candidates
|
||||
"""
|
||||
pass
|
||||
self.transport.remote_candidates = candidates
|
||||
|
||||
def on_stanza(self, stanza, content, error, action):
|
||||
"""
|
||||
|
@ -109,12 +110,15 @@ class JingleContent(object):
|
|||
for callback in self.callbacks[action]:
|
||||
callback(stanza, content, error, action)
|
||||
|
||||
def __on_transport_replace(self, stanza, content, error, action):
|
||||
content.addChild(node=self.transport.make_transport())
|
||||
|
||||
def __on_transport_info(self, stanza, content, error, action):
|
||||
"""
|
||||
Got a new transport candidate
|
||||
"""
|
||||
candidates = self.transport.parse_transport_stanza(
|
||||
content.getTag('transport'))
|
||||
content.getTag('transport'))
|
||||
if candidates:
|
||||
self.add_remote_candidates(candidates)
|
||||
|
||||
|
@ -134,6 +138,17 @@ class JingleContent(object):
|
|||
content.addChild(node=self.transport.make_transport([candidate]))
|
||||
self.session.send_transport_info(content)
|
||||
|
||||
def send_error_candidate(self):
|
||||
"""
|
||||
Sends a candidate-error when we can't connect to a candidate.
|
||||
"""
|
||||
content = self.__content()
|
||||
tp = self.transport.make_transport(add_candidates=False)
|
||||
tp.addChild(name='candidate-error')
|
||||
content.addChild(node=tp)
|
||||
self.session.send_transport_info(content)
|
||||
|
||||
|
||||
def send_description_info(self):
|
||||
content = self.__content()
|
||||
self._fill_content(content)
|
||||
|
|
|
@ -0,0 +1,375 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## 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/>.
|
||||
##
|
||||
|
||||
|
||||
"""
|
||||
Handles Jingle File Transfer (XEP 0234)
|
||||
"""
|
||||
|
||||
import gajim
|
||||
import xmpp
|
||||
from jingle_content import contents, JingleContent
|
||||
from jingle_transport import *
|
||||
from common import helpers
|
||||
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
|
||||
from common.connection_handlers_events import FileRequestReceivedEvent
|
||||
import threading
|
||||
import logging
|
||||
from jingle_ftstates import *
|
||||
log = logging.getLogger('gajim.c.jingle_ft')
|
||||
|
||||
STATE_NOT_STARTED = 0
|
||||
STATE_INITIALIZED = 1
|
||||
# We send the candidates and we are waiting for a reply
|
||||
STATE_CAND_SENT = 2
|
||||
# We received the candidates and we are waiting to reply
|
||||
STATE_CAND_RECEIVED = 3
|
||||
# We have sent and received the candidates
|
||||
# This also includes any candidate-error received or sent
|
||||
STATE_CAND_SENT_AND_RECEIVED = 4
|
||||
STATE_TRANSPORT_REPLACE = 5
|
||||
# We are transfering the file
|
||||
STATE_TRANSFERING = 6
|
||||
|
||||
|
||||
class JingleFileTransfer(JingleContent):
|
||||
def __init__(self, session, transport=None, file_props=None,
|
||||
use_security=False):
|
||||
JingleContent.__init__(self, session, transport)
|
||||
|
||||
log.info("transport value: %s" % transport)
|
||||
|
||||
# events we might be interested in
|
||||
self.callbacks['session-initiate'] += [self.__on_session_initiate]
|
||||
self.callbacks['session-initiate-sent'] += [self.__on_session_initiate_sent]
|
||||
self.callbacks['content-add'] += [self.__on_session_initiate]
|
||||
self.callbacks['session-accept'] += [self.__on_session_accept]
|
||||
self.callbacks['session-terminate'] += [self.__on_session_terminate]
|
||||
self.callbacks['session-info'] += [self.__on_session_info]
|
||||
self.callbacks['transport-accept'] += [self.__on_transport_accept]
|
||||
self.callbacks['transport-replace'] += [self.__on_transport_replace]
|
||||
self.callbacks['session-accept-sent'] += [self.__transport_setup]
|
||||
# fallback transport method
|
||||
self.callbacks['transport-reject'] += [self.__on_transport_reject]
|
||||
self.callbacks['transport-info'] += [self.__on_transport_info]
|
||||
self.callbacks['iq-result'] += [self.__on_iq_result]
|
||||
|
||||
self.use_security = use_security
|
||||
|
||||
self.file_props = file_props
|
||||
if file_props is None:
|
||||
self.weinitiate = False
|
||||
else:
|
||||
self.weinitiate = True
|
||||
|
||||
if self.file_props is not None:
|
||||
self.file_props['sender'] = session.ourjid
|
||||
self.file_props['receiver'] = session.peerjid
|
||||
self.file_props['session-type'] = 'jingle'
|
||||
self.file_props['session-sid'] = session.sid
|
||||
self.file_props['transfered_size'] = []
|
||||
|
||||
log.info("FT request: %s" % file_props)
|
||||
|
||||
if transport is None:
|
||||
self.transport = JingleTransportSocks5()
|
||||
self.transport.set_connection(session.connection)
|
||||
self.transport.set_file_props(self.file_props)
|
||||
self.transport.set_our_jid(session.ourjid)
|
||||
log.info('ourjid: %s' % session.ourjid)
|
||||
|
||||
if self.file_props is not None:
|
||||
self.file_props['sid'] = self.transport.sid
|
||||
|
||||
self.session = session
|
||||
self.media = 'file'
|
||||
self.nominated_cand = {}
|
||||
|
||||
self.state = STATE_NOT_STARTED
|
||||
self.states = {STATE_INITIALIZED : StateInitialized(self),
|
||||
STATE_CAND_SENT : StateCandSent(self),
|
||||
STATE_CAND_RECEIVED : StateCandReceived(self),
|
||||
STATE_TRANSFERING : StateTransfering(self),
|
||||
STATE_TRANSPORT_REPLACE : StateTransportReplace(self),
|
||||
STATE_CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
|
||||
}
|
||||
|
||||
def __state_changed(self, nextstate, args=None):
|
||||
# Executes the next state action and sets the next state
|
||||
st = self.states[nextstate]
|
||||
st.action(args)
|
||||
self.state = nextstate
|
||||
|
||||
def __on_session_initiate(self, stanza, content, error, action):
|
||||
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
|
||||
conn=self.session.connection, stanza=stanza, jingle_content=content,
|
||||
FT_content=self))
|
||||
def __on_session_initiate_sent(self, stanza, content, error, action):
|
||||
# Calculate file_hash in a new thread
|
||||
self.hashThread = threading.Thread(target=self.__calcHash)
|
||||
self.hashThread.start()
|
||||
|
||||
def __calcHash(self):
|
||||
if self.session.hash_algo == None:
|
||||
return
|
||||
try:
|
||||
file_ = open(self.file_props['file-name'], 'r')
|
||||
except:
|
||||
# can't open file
|
||||
return
|
||||
h = xmpp.Hashes()
|
||||
hash_ = h.calculateHash(self.session.hash_algo, file_)
|
||||
if not hash_:
|
||||
# Hash alogrithm not supported
|
||||
return
|
||||
self.file_props['hash'] = hash_
|
||||
h.addHash(hash_, self.session.hash_algo)
|
||||
checksum = xmpp.Node(tag='checksum',
|
||||
payload=[xmpp.Node(tag='file', payload=[h])])
|
||||
checksum.setNamespace(xmpp.NS_JINGLE_FILE_TRANSFER)
|
||||
# Send hash in a session info
|
||||
self.session.__session_info(checksum )
|
||||
|
||||
|
||||
def __on_session_accept(self, stanza, content, error, action):
|
||||
log.info("__on_session_accept")
|
||||
con = self.session.connection
|
||||
security = content.getTag('security')
|
||||
if not security: # responder can not verify our fingerprint
|
||||
self.use_security = False
|
||||
|
||||
|
||||
if self.state == STATE_TRANSPORT_REPLACE:
|
||||
# We ack the session accept
|
||||
response = stanza.buildReply('result')
|
||||
response.delChild(response.getQuery())
|
||||
con.connection.send(response)
|
||||
# We send the file
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
self.file_props['streamhosts'] = self.transport.remote_candidates
|
||||
for host in self.file_props['streamhosts']:
|
||||
host['initiator'] = self.session.initiator
|
||||
host['target'] = self.session.responder
|
||||
host['sid'] = self.file_props['sid']
|
||||
|
||||
response = stanza.buildReply('result')
|
||||
response.delChild(response.getQuery())
|
||||
con.connection.send(response)
|
||||
|
||||
if not gajim.socks5queue.get_file_props(
|
||||
self.session.connection.name, self.file_props['sid']):
|
||||
gajim.socks5queue.add_file_props(self.session.connection.name,
|
||||
self.file_props)
|
||||
fingerprint = None
|
||||
if self.use_security:
|
||||
fingerprint = 'client'
|
||||
if self.transport.type == TransportType.SOCKS5:
|
||||
gajim.socks5queue.connect_to_hosts(self.session.connection.name,
|
||||
self.file_props['sid'], self.on_connect,
|
||||
self._on_connect_error, fingerprint=fingerprint,
|
||||
receiving=False)
|
||||
return
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
def __on_session_terminate(self, stanza, content, error, action):
|
||||
log.info("__on_session_terminate")
|
||||
|
||||
def __on_session_info(self, stanza, content, error, action):
|
||||
pass
|
||||
|
||||
def __on_transport_accept(self, stanza, content, error, action):
|
||||
log.info("__on_transport_accept")
|
||||
|
||||
def __on_transport_replace(self, stanza, content, error, action):
|
||||
log.info("__on_transport_replace")
|
||||
|
||||
def __on_transport_reject(self, stanza, content, error, action):
|
||||
log.info("__on_transport_reject")
|
||||
|
||||
def __on_transport_info(self, stanza, content, error, action):
|
||||
log.info("__on_transport_info")
|
||||
|
||||
if content.getTag('transport').getTag('candidate-error'):
|
||||
self.nominated_cand['peer-cand'] = False
|
||||
if self.state == STATE_CAND_SENT:
|
||||
if not self.nominated_cand['our-cand'] and \
|
||||
not self.nominated_cand['peer-cand']:
|
||||
if not self.weinitiate:
|
||||
return
|
||||
self.__state_changed(STATE_TRANSPORT_REPLACE)
|
||||
else:
|
||||
response = stanza.buildReply('result')
|
||||
response.delChild(response.getQuery())
|
||||
self.session.connection.connection.send(response)
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
raise xmpp.NodeProcessed
|
||||
else:
|
||||
args = {'candError' : True}
|
||||
self.__state_changed(STATE_CAND_RECEIVED, args)
|
||||
return
|
||||
|
||||
if content.getTag('transport').getTag('activated'):
|
||||
self.state = STATE_TRANSFERING
|
||||
jid = gajim.get_jid_without_resource(self.session.ourjid)
|
||||
gajim.socks5queue.send_file(self.file_props,
|
||||
self.session.connection.name, 'client')
|
||||
return
|
||||
|
||||
args = {'content' : content,
|
||||
'sendCand' : False}
|
||||
if self.state == STATE_CAND_SENT:
|
||||
self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args)
|
||||
response = stanza.buildReply('result')
|
||||
response.delChild(response.getQuery())
|
||||
self.session.connection.connection.send(response)
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
raise xmpp.NodeProcessed
|
||||
else:
|
||||
self.__state_changed(STATE_CAND_RECEIVED, args)
|
||||
|
||||
|
||||
|
||||
def __on_iq_result(self, stanza, content, error, action):
|
||||
log.info("__on_iq_result")
|
||||
|
||||
if self.state == STATE_NOT_STARTED:
|
||||
self.__state_changed(STATE_INITIALIZED)
|
||||
elif self.state == STATE_CAND_SENT_AND_RECEIVED:
|
||||
if not self.nominated_cand['our-cand'] and \
|
||||
not self.nominated_cand['peer-cand']:
|
||||
if not self.weinitiate:
|
||||
return
|
||||
self.__state_changed(STATE_TRANSPORT_REPLACE)
|
||||
return
|
||||
# initiate transfer
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
|
||||
def __transport_setup(self, stanza=None, content=None, error=None,
|
||||
action=None):
|
||||
# Sets up a few transport specific things for the file transfer
|
||||
|
||||
if self.transport.type == TransportType.IBB:
|
||||
# No action required, just set the state to transfering
|
||||
self.state = STATE_TRANSFERING
|
||||
|
||||
|
||||
def on_connect(self, streamhost):
|
||||
"""
|
||||
send candidate-used stanza
|
||||
"""
|
||||
log.info('send_candidate_used')
|
||||
if streamhost is None:
|
||||
return
|
||||
args = {'streamhost' : streamhost,
|
||||
'sendCand' : True}
|
||||
|
||||
self.nominated_cand['our-cand'] = streamhost
|
||||
self.__sendCand(args)
|
||||
|
||||
def _on_connect_error(self, sid):
|
||||
log.info('connect error, sid=' + sid)
|
||||
args = {'candError' : True}
|
||||
self.__sendCand(args)
|
||||
|
||||
def __sendCand(self, args):
|
||||
if self.state == STATE_CAND_RECEIVED:
|
||||
self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args)
|
||||
else:
|
||||
self.__state_changed(STATE_CAND_SENT, args)
|
||||
|
||||
def _fill_content(self, content):
|
||||
description_node = xmpp.simplexml.Node(
|
||||
tag=xmpp.NS_JINGLE_FILE_TRANSFER + ' description')
|
||||
|
||||
sioffer = xmpp.simplexml.Node(tag='offer')
|
||||
file_tag = sioffer.setTag('file', namespace=xmpp.NS_FILE)
|
||||
file_tag.setAttr('name', self.file_props['name'])
|
||||
file_tag.setAttr('size', self.file_props['size'])
|
||||
desc = file_tag.setTag('desc')
|
||||
if 'desc' in self.file_props:
|
||||
desc.setData(self.file_props['desc'])
|
||||
|
||||
description_node.addChild(node=sioffer)
|
||||
|
||||
if self.use_security:
|
||||
security = xmpp.simplexml.Node(
|
||||
tag=xmpp.NS_JINGLE_XTLS + ' security')
|
||||
# TODO: add fingerprint element
|
||||
for m in ('x509', ): # supported authentication methods
|
||||
method = xmpp.simplexml.Node(tag='method')
|
||||
method.setAttr('name', m)
|
||||
security.addChild(node=method)
|
||||
content.addChild(node=security)
|
||||
|
||||
content.addChild(node=description_node)
|
||||
|
||||
def _store_socks5_sid(self, sid, hash_id):
|
||||
# callback from socsk5queue.start_listener
|
||||
self.file_props['hash'] = hash_id
|
||||
|
||||
def _listen_host(self):
|
||||
|
||||
receiver = self.file_props['receiver']
|
||||
sender = self.file_props['sender']
|
||||
sha_str = helpers.get_auth_sha(self.file_props['sid'], sender,
|
||||
receiver)
|
||||
self.file_props['sha_str'] = sha_str
|
||||
|
||||
port = gajim.config.get('file_transfers_port')
|
||||
|
||||
fingerprint = None
|
||||
if self.use_security:
|
||||
fingerprint = 'server'
|
||||
|
||||
if self.weinitiate:
|
||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||
self._store_socks5_sid, self.file_props,
|
||||
fingerprint=fingerprint, type='sender')
|
||||
else:
|
||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||
self._store_socks5_sid, self.file_props,
|
||||
fingerprint=fingerprint, type='receiver')
|
||||
|
||||
if not listener:
|
||||
# send error message, notify the user
|
||||
return
|
||||
def isOurCandUsed(self):
|
||||
'''
|
||||
If this method returns true then the candidate we nominated will be
|
||||
used, if false, the candidate nominated by peer will be used
|
||||
'''
|
||||
|
||||
if self.nominated_cand['peer-cand'] == False:
|
||||
return True
|
||||
if self.nominated_cand['our-cand'] == False:
|
||||
return False
|
||||
|
||||
peer_pr = int(self.nominated_cand['peer-cand']['priority'])
|
||||
our_pr = int(self.nominated_cand['our-cand']['priority'])
|
||||
|
||||
if peer_pr != our_pr:
|
||||
return our_pr > peer_pr
|
||||
else:
|
||||
return self.weinitiate
|
||||
|
||||
|
||||
def get_content(desc):
|
||||
return JingleFileTransfer
|
||||
|
||||
contents[xmpp.NS_JINGLE_FILE_TRANSFER] = get_content
|
|
@ -0,0 +1,244 @@
|
|||
##
|
||||
## Copyright (C) 2006 Gajim Team
|
||||
##
|
||||
## This program 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 2 only.
|
||||
##
|
||||
## This program 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.
|
||||
##
|
||||
|
||||
import gajim
|
||||
import xmpp
|
||||
from jingle_transport import *
|
||||
|
||||
class JingleFileTransferStates:
|
||||
|
||||
# This class implements the state machine design pattern
|
||||
|
||||
def __init__(self, jingleft):
|
||||
|
||||
self.jft = jingleft
|
||||
|
||||
def action(self, args=None):
|
||||
'''
|
||||
This method MUST be overriden by a subclass
|
||||
'''
|
||||
raise Exception('This is an abstract method!!')
|
||||
|
||||
|
||||
class StateInitialized(JingleFileTransferStates):
|
||||
|
||||
'''
|
||||
This state initializes the file transfer
|
||||
'''
|
||||
|
||||
def action(self, args=None):
|
||||
self.jft._listen_host()
|
||||
if self.jft.weinitiate:
|
||||
# update connection's fileprops
|
||||
self.jft.session.connection.files_props[self.jft.file_props['sid']] = \
|
||||
self.jft.file_props
|
||||
# Listen on configured port for file transfer
|
||||
else:
|
||||
# Add file_props to the queue
|
||||
if not gajim.socks5queue.get_file_props(
|
||||
self.jft.session.connection.name, self.jft.file_props['sid']):
|
||||
gajim.socks5queue.add_file_props(
|
||||
self.jft.session.connection.name,
|
||||
self.jft.file_props)
|
||||
fingerprint = None
|
||||
if self.jft.use_security:
|
||||
fingerprint = 'client'
|
||||
# Connect to the candidate host, on success call on_connect method
|
||||
gajim.socks5queue.connect_to_hosts(
|
||||
self.jft.session.connection.name,
|
||||
self.jft.file_props['sid'], self.jft.on_connect,
|
||||
self.jft._on_connect_error, fingerprint=fingerprint)
|
||||
|
||||
|
||||
class StateCandSent(JingleFileTransferStates):
|
||||
|
||||
'''
|
||||
This state sends our nominated candidate
|
||||
'''
|
||||
|
||||
def _sendCand(self, args):
|
||||
if 'candError' in args:
|
||||
self.jft.nominated_cand['our-cand'] = False
|
||||
self.jft.send_error_candidate()
|
||||
return
|
||||
# Send candidate used
|
||||
streamhost = args['streamhost']
|
||||
self.jft.nominated_cand['our-cand'] = streamhost
|
||||
|
||||
content = xmpp.Node('content')
|
||||
content.setAttr('creator', 'initiator')
|
||||
content.setAttr('name', self.jft.name)
|
||||
|
||||
transport = xmpp.Node('transport')
|
||||
transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM)
|
||||
transport.setAttr('sid', self.jft.transport.sid)
|
||||
|
||||
candidateused = xmpp.Node('candidate-used')
|
||||
candidateused.setAttr('cid', streamhost['cid'])
|
||||
|
||||
transport.addChild(node=candidateused)
|
||||
content.addChild(node=transport)
|
||||
|
||||
self.jft.session.send_transport_info(content)
|
||||
|
||||
|
||||
def action(self, args=None):
|
||||
self._sendCand(args)
|
||||
|
||||
class StateCandReceived(JingleFileTransferStates):
|
||||
|
||||
'''
|
||||
This state happens when we receive a candidate.
|
||||
It takes the arguments: canError if we receive a candidate-error
|
||||
'''
|
||||
|
||||
def _recvCand(self, args):
|
||||
if 'candError' in args:
|
||||
return
|
||||
content = args['content']
|
||||
streamhost_cid = content.getTag('transport').getTag('candidate-used').\
|
||||
getAttr('cid')
|
||||
streamhost_used = None
|
||||
for cand in self.jft.transport.candidates:
|
||||
if cand['candidate_id'] == streamhost_cid:
|
||||
streamhost_used = cand
|
||||
break
|
||||
if streamhost_used == None:
|
||||
log.info("unknow streamhost")
|
||||
return
|
||||
# We save the candidate nominated by peer
|
||||
self.jft.nominated_cand['peer-cand'] = streamhost_used
|
||||
|
||||
|
||||
|
||||
def action(self, args=None):
|
||||
self._recvCand(args)
|
||||
|
||||
class StateCandSentAndRecv( StateCandSent, StateCandReceived):
|
||||
|
||||
'''
|
||||
This state happens when we have received and sent the candidates.
|
||||
It takes the boolean argument: sendCand in order to decide whether
|
||||
we should execute the action of when we receive or send a candidate.
|
||||
'''
|
||||
|
||||
def action(self, args=None):
|
||||
|
||||
if args['sendCand']:
|
||||
self._sendCand(args)
|
||||
else:
|
||||
self._recvCand(args)
|
||||
|
||||
class StateTransportReplace(JingleFileTransferStates):
|
||||
|
||||
'''
|
||||
This state initiates transport replace
|
||||
'''
|
||||
|
||||
def action(self, args=None):
|
||||
self.jft.session.transport_replace()
|
||||
|
||||
class StateTransfering(JingleFileTransferStates):
|
||||
|
||||
'''
|
||||
This state will start the transfer depeding on the type of transport
|
||||
we have.
|
||||
'''
|
||||
|
||||
def __start_IBB_transfer(self, con):
|
||||
con.files_props[self.jft.file_props['sid']] = \
|
||||
self.jft.file_props
|
||||
fp = open(self.jft.file_props['file-name'], 'r')
|
||||
con.OpenStream( self.jft.transport.sid,
|
||||
self.jft.session.peerjid, fp, blocksize=4096)
|
||||
|
||||
def __start_SOCK5_transfer(self):
|
||||
# It tells wether we start the transfer as client or server
|
||||
mode = None
|
||||
|
||||
if self.jft.isOurCandUsed():
|
||||
mode = 'client'
|
||||
streamhost_used = self.jft.nominated_cand['our-cand']
|
||||
else:
|
||||
mode = 'server'
|
||||
streamhost_used = self.jft.nominated_cand['peer-cand']
|
||||
|
||||
if streamhost_used['type'] == 'proxy':
|
||||
self.jft.file_props['is_a_proxy'] = True
|
||||
# This needs to be changed when requesting
|
||||
if self.jft.weinitiate:
|
||||
self.jft.file_props['proxy_sender'] = streamhost_used['initiator']
|
||||
self.jft.file_props['proxy_receiver'] = streamhost_used['target']
|
||||
else:
|
||||
self.jft.file_props['proxy_sender'] = streamhost_used['target']
|
||||
self.jft.file_props['proxy_receiver'] = streamhost_used['initiator']
|
||||
|
||||
# This needs to be changed when requesting
|
||||
if not self.jft.weinitiate and streamhost_used['type'] == 'proxy':
|
||||
r = gajim.socks5queue.readers
|
||||
for reader in r:
|
||||
if r[reader].host == streamhost_used['host'] and \
|
||||
r[reader].connected:
|
||||
return
|
||||
|
||||
# This needs to be changed when requesting
|
||||
if self.jft.weinitiate and streamhost_used['type'] == 'proxy':
|
||||
s = gajim.socks5queue.senders
|
||||
for sender in s:
|
||||
if s[sender].host == streamhost_used['host'] and \
|
||||
s[sender].connected:
|
||||
return
|
||||
|
||||
if streamhost_used['type'] == 'proxy':
|
||||
self.jft.file_props['streamhost-used'] = True
|
||||
streamhost_used['sid'] = self.jft.file_props['sid']
|
||||
self.jft.file_props['streamhosts'] = []
|
||||
self.jft.file_props['streamhosts'].append(streamhost_used)
|
||||
self.jft.file_props['proxyhosts'] = []
|
||||
self.jft.file_props['proxyhosts'].append(streamhost_used)
|
||||
|
||||
# This needs to be changed when requesting
|
||||
if self.jft.weinitiate:
|
||||
gajim.socks5queue.idx += 1
|
||||
idx = gajim.socks5queue.idx
|
||||
sockobj = Socks5SenderClient(gajim.idlequeue, idx,
|
||||
gajim.socks5queue, _sock=None,
|
||||
host=str(streamhost_used['host']),
|
||||
port=int(streamhost_used['port']), fingerprint=None,
|
||||
connected=False, file_props=self.jft.file_props)
|
||||
else:
|
||||
sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used,
|
||||
sid=self.jft.file_props['sid'],
|
||||
file_props=self.jft.file_props, fingerprint=None)
|
||||
sockobj.proxy = True
|
||||
sockobj.streamhost = streamhost_used
|
||||
gajim.socks5queue.add_sockobj(self.jft.session.connection.name,
|
||||
sockobj, 'sender')
|
||||
streamhost_used['idx'] = sockobj.queue_idx
|
||||
# If we offered the nominated candidate used, we activate
|
||||
# the proxy
|
||||
if not self.jft.isOurCandUsed():
|
||||
gajim.socks5queue.on_success[self.jft.file_props['sid']] = \
|
||||
self.jft.transport._on_proxy_auth_ok
|
||||
# TODO: add on failure
|
||||
else:
|
||||
jid = gajim.get_jid_without_resource(self.jft.session.ourjid)
|
||||
gajim.socks5queue.send_file(self.jft.file_props,
|
||||
self.jft.session.connection.name, mode)
|
||||
|
||||
def action(self, args=None):
|
||||
if self.jft.transport.type == TransportType.IBB:
|
||||
self.__start_IBB_transfer(self.jft.session.connection)
|
||||
|
||||
elif self.jft.transport.type == TransportType.SOCKS5:
|
||||
self.__start_SOCK5_transfer()
|
|
@ -38,7 +38,7 @@ log = logging.getLogger('gajim.c.jingle_rtp')
|
|||
class JingleRTPContent(JingleContent):
|
||||
def __init__(self, session, media, transport=None):
|
||||
if transport is None:
|
||||
transport = JingleTransportICEUDP()
|
||||
transport = JingleTransportICEUDP(None)
|
||||
JingleContent.__init__(self, session, transport)
|
||||
self.media = media
|
||||
self._dtmf_running = False
|
||||
|
|
|
@ -28,9 +28,13 @@ Handles Jingle sessions (XEP 0166)
|
|||
|
||||
import gajim #Get rid of that?
|
||||
import xmpp
|
||||
from jingle_transport import get_jingle_transport
|
||||
from jingle_transport import get_jingle_transport, JingleTransportIBB
|
||||
from jingle_content import get_jingle_content, JingleContentSetupException
|
||||
from jingle_content import JingleContent
|
||||
from jingle_ft import STATE_TRANSPORT_REPLACE
|
||||
from common.connection_handlers_events import *
|
||||
import logging
|
||||
log = logging.getLogger("gajim.c.jingle_session")
|
||||
|
||||
# FIXME: Move it to JingleSession.States?
|
||||
class JingleStates(object):
|
||||
|
@ -59,7 +63,7 @@ class JingleSession(object):
|
|||
negotiated between an initiator and a responder.
|
||||
"""
|
||||
|
||||
def __init__(self, con, weinitiate, jid, sid=None):
|
||||
def __init__(self, con, weinitiate, jid, iq_id=None, sid=None):
|
||||
"""
|
||||
con -- connection object,
|
||||
weinitiate -- boolean, are we the initiator?
|
||||
|
@ -85,8 +89,21 @@ class JingleSession(object):
|
|||
sid = con.connection.getAnID()
|
||||
self.sid = sid # sessionid
|
||||
|
||||
self.accepted = True # is this session accepted by user
|
||||
# iq stanza id, used to determine which sessions to summon callback
|
||||
# later on when iq-result stanza arrives
|
||||
if iq_id is not None:
|
||||
self.iq_ids = [iq_id]
|
||||
else:
|
||||
self.iq_ids = []
|
||||
|
||||
|
||||
self.accepted = True # is this session accepted by user
|
||||
# Hash algorithm that we are using to calculate the integrity of the
|
||||
# file. Could be 'md5', 'sha-1', etc...
|
||||
self.hash_algo = None
|
||||
self.file_hash = None
|
||||
# Tells whether this session is a file transfer or not
|
||||
self.session_type_FT = False
|
||||
# callbacks to call on proper contents
|
||||
# use .prepend() to add new callbacks, especially when you're going
|
||||
# to send error instead of ack
|
||||
|
@ -101,7 +118,7 @@ class JingleSession(object):
|
|||
'description-info': [self.__broadcast, self.__ack], #TODO
|
||||
'security-info': [self.__ack], #TODO
|
||||
'session-accept': [self.__on_session_accept, self.__on_content_accept,
|
||||
self.__broadcast, self.__ack],
|
||||
self.__broadcast],
|
||||
'session-info': [self.__broadcast, self.__on_session_info, self.__ack],
|
||||
'session-initiate': [self.__on_session_initiate, self.__broadcast,
|
||||
self.__ack],
|
||||
|
@ -111,9 +128,14 @@ class JingleSession(object):
|
|||
'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO
|
||||
'transport-accept': [self.__ack], #TODO
|
||||
'transport-reject': [self.__ack], #TODO
|
||||
'iq-result': [],
|
||||
'iq-result': [self.__broadcast],
|
||||
'iq-error': [self.__on_error],
|
||||
}
|
||||
|
||||
|
||||
def collect_iq_id(self, iq_id):
|
||||
if iq_id is not None:
|
||||
self.iq_ids.append(iq_id)
|
||||
|
||||
def approve_session(self):
|
||||
"""
|
||||
|
@ -128,15 +150,23 @@ class JingleSession(object):
|
|||
reason = xmpp.Node('reason')
|
||||
reason.addChild('decline')
|
||||
self._session_terminate(reason)
|
||||
|
||||
def cancel_session(self):
|
||||
"""
|
||||
Called when user declines session in UI (when we aren't the initiator)
|
||||
"""
|
||||
reason = xmpp.Node('reason')
|
||||
reason.addChild('cancel')
|
||||
self._session_terminate(reason)
|
||||
|
||||
def approve_content(self, media):
|
||||
content = self.get_content(media)
|
||||
def approve_content(self, media, name=None):
|
||||
content = self.get_content(media, name)
|
||||
if content:
|
||||
content.accepted = True
|
||||
self.on_session_state_changed(content)
|
||||
|
||||
def reject_content(self, media):
|
||||
content = self.get_content(media)
|
||||
def reject_content(self, media, name=None):
|
||||
content = self.get_content(media, name)
|
||||
if content:
|
||||
if self.state == JingleStates.active:
|
||||
self.__content_reject(content)
|
||||
|
@ -154,13 +184,14 @@ class JingleSession(object):
|
|||
reason.addChild('cancel')
|
||||
self._session_terminate(reason)
|
||||
|
||||
def get_content(self, media=None):
|
||||
def get_content(self, media=None, name=None):
|
||||
if media is None:
|
||||
return
|
||||
|
||||
for content in self.contents.values():
|
||||
if content.media == media:
|
||||
return content
|
||||
if name is None or content.name == name:
|
||||
return content
|
||||
|
||||
def add_content(self, name, content, creator='we'):
|
||||
"""
|
||||
|
@ -195,12 +226,22 @@ class JingleSession(object):
|
|||
content = self.contents[(creator, name)]
|
||||
self.__content_remove(content, reason)
|
||||
self.contents[(creator, name)].destroy()
|
||||
if not self.contents:
|
||||
self.end_session()
|
||||
|
||||
def modify_content(self, creator, name, *someother):
|
||||
"""
|
||||
We do not need this now
|
||||
"""
|
||||
pass
|
||||
def modify_content(self, creator, name, transport = None):
|
||||
'''
|
||||
Currently used for transport replacement
|
||||
'''
|
||||
|
||||
content = self.contents[(creator,name)]
|
||||
transport.set_sid(content.transport.sid)
|
||||
transport.set_file_props(content.transport.file_props)
|
||||
content.transport = transport
|
||||
# The content will have to be resend now that it is modified
|
||||
content.sent = False
|
||||
content.accepted = True
|
||||
|
||||
|
||||
def on_session_state_changed(self, content=None):
|
||||
if self.state == JingleStates.ended:
|
||||
|
@ -216,10 +257,15 @@ class JingleSession(object):
|
|||
elif content and self.weinitiate:
|
||||
self.__content_accept(content)
|
||||
elif self.state == JingleStates.active:
|
||||
# We can either send a content-add or a content-accept
|
||||
# We can either send a content-add or a content-accept. However, if
|
||||
# we are sending a file we can only use session_initiate.
|
||||
if not content:
|
||||
return
|
||||
if (content.creator == 'initiator') == self.weinitiate:
|
||||
we_created_content = (content.creator == 'initiator') \
|
||||
== self.weinitiate
|
||||
if we_created_content and content.media == 'file':
|
||||
self.__session_initiate()
|
||||
if we_created_content:
|
||||
# We initiated this content. It's a pending content-add.
|
||||
self.__content_add(content)
|
||||
else:
|
||||
|
@ -261,6 +307,7 @@ class JingleSession(object):
|
|||
stanza, jingle = self.__make_jingle('transport-info')
|
||||
jingle.addChild(node=content)
|
||||
self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(stanza.getID())
|
||||
|
||||
def send_description_info(self, content):
|
||||
assert self.state != JingleStates.ended
|
||||
|
@ -286,7 +333,7 @@ class JingleSession(object):
|
|||
self.__send_error(stanza, 'bad-request')
|
||||
return
|
||||
# FIXME: If we aren't initiated and it's not a session-initiate...
|
||||
if action != 'session-initiate' and self.state == JingleStates.ended:
|
||||
if action not in ['session-initiate','session-terminate'] and self.state == JingleStates.ended:
|
||||
self.__send_error(stanza, 'item-not-found', 'unknown-session')
|
||||
return
|
||||
else:
|
||||
|
@ -311,6 +358,7 @@ class JingleSession(object):
|
|||
Default callback for action stanzas -- simple ack and stop processing
|
||||
"""
|
||||
response = stanza.buildReply('result')
|
||||
response.delChild(response.getQuery())
|
||||
self.connection.connection.send(response)
|
||||
|
||||
def __on_error(self, stanza, jingle, error, action):
|
||||
|
@ -325,18 +373,42 @@ class JingleSession(object):
|
|||
error_name = child.getName()
|
||||
self.__dispatch_error(error_name, text, error.getAttr('type'))
|
||||
# FIXME: Not sure when we would want to do that...
|
||||
def transport_replace(self):
|
||||
transport = JingleTransportIBB()
|
||||
# For debug only, delete this and replace for a function
|
||||
# that will identify contents by its sid
|
||||
for creator, name in self.contents.keys():
|
||||
self.modify_content(creator, name, transport)
|
||||
cont = self.contents[(creator, name)]
|
||||
cont.transport = transport
|
||||
|
||||
stanza, jingle = self.__make_jingle('transport-replace')
|
||||
self.__append_contents(jingle)
|
||||
self.__broadcast(stanza, jingle, None, 'transport-replace')
|
||||
self.connection.connection.send(stanza)
|
||||
self.state = JingleStates.pending
|
||||
|
||||
|
||||
def __on_transport_replace(self, stanza, jingle, error, action):
|
||||
for content in jingle.iterTags('content'):
|
||||
creator = content['creator']
|
||||
name = content['name']
|
||||
if (creator, name) in self.contents:
|
||||
transport_ns = content.getTag('transport').getNamespace()
|
||||
if transport_ns == xmpp.JINGLE_ICE_UDP:
|
||||
if transport_ns == xmpp.NS_JINGLE_ICE_UDP:
|
||||
# FIXME: We don't manage anything else than ICE-UDP now...
|
||||
# What was the previous transport?!?
|
||||
# Anyway, content's transport is not modifiable yet
|
||||
pass
|
||||
elif transport_ns == xmpp.NS_JINGLE_IBB:
|
||||
|
||||
transport = JingleTransportIBB()
|
||||
self.modify_content(creator, name, transport)
|
||||
self.state = JingleStates.pending
|
||||
self.contents[(creator,name)].state = STATE_TRANSPORT_REPLACE
|
||||
self.__ack(stanza, jingle, error, action)
|
||||
self.__session_accept()
|
||||
|
||||
else:
|
||||
stanza, jingle = self.__make_jingle('transport-reject')
|
||||
content = jingle.setTag('content', attrs={'creator': creator,
|
||||
|
@ -357,9 +429,22 @@ class JingleSession(object):
|
|||
def __on_session_info(self, stanza, jingle, error, action):
|
||||
# TODO: ringing, active, (un)hold, (un)mute
|
||||
payload = jingle.getPayload()
|
||||
if payload:
|
||||
self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify')
|
||||
raise xmpp.NodeProcessed
|
||||
for p in payload:
|
||||
if p.getName() == 'checksum':
|
||||
hashes = p.getTag('file').getTag(name='hashes',
|
||||
namespace=xmpp.NS_HASHES)
|
||||
for hash in hashes.getChildren():
|
||||
algo = hash.getAttr('algo')
|
||||
if algo in xmpp.Hashes.supported:
|
||||
self.hash_algo = algo
|
||||
data = hash.getData()
|
||||
# This only works because there is only one session
|
||||
# per file in jingleFT
|
||||
self.file_hash = data
|
||||
raise xmpp.NodeProcessed
|
||||
self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify')
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
|
||||
def __on_content_remove(self, stanza, jingle, error, action):
|
||||
for content in jingle.iterTags('content'):
|
||||
|
@ -382,6 +467,7 @@ class JingleSession(object):
|
|||
if self.state != JingleStates.pending:
|
||||
raise OutOfOrder
|
||||
self.state = JingleStates.active
|
||||
|
||||
|
||||
def __on_content_accept(self, stanza, jingle, error, action):
|
||||
"""
|
||||
|
@ -429,19 +515,25 @@ class JingleSession(object):
|
|||
# subscription) and the receiver has a policy of not communicating via
|
||||
# Jingle with unknown entities, it SHOULD return a <service-unavailable/>
|
||||
# error.
|
||||
|
||||
# Check if there's already a session with this user:
|
||||
for session in self.connection.iter_jingle_sessions(self.peerjid):
|
||||
if not session is self:
|
||||
reason = xmpp.Node('reason')
|
||||
alternative_session = reason.setTag('alternative-session')
|
||||
alternative_session.setTagData('sid', session.sid)
|
||||
self.__ack(stanza, jingle, error, action)
|
||||
self._session_terminate(reason)
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
|
||||
|
||||
# Lets check what kind of jingle session does the peer want
|
||||
contents, contents_rejected, reason_txt = self.__parse_contents(jingle)
|
||||
|
||||
|
||||
# If we are not receivin a file
|
||||
# Check if there's already a session with this user:
|
||||
if contents[0][0] != 'file':
|
||||
for session in self.connection.iter_jingle_sessions(self.peerjid):
|
||||
if not session is self:
|
||||
reason = xmpp.Node('reason')
|
||||
alternative_session = reason.setTag('alternative-session')
|
||||
alternative_session.setTagData('sid', session.sid)
|
||||
self.__ack(stanza, jingle, error, action)
|
||||
self._session_terminate(reason)
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
|
||||
|
||||
# If there's no content we understand...
|
||||
if not contents:
|
||||
|
@ -462,6 +554,17 @@ class JingleSession(object):
|
|||
"""
|
||||
Broadcast the stanza contents to proper content handlers
|
||||
"""
|
||||
#if jingle is None: # it is a iq-result stanza
|
||||
# for cn in self.contents.values():
|
||||
# cn.on_stanza(stanza, None, error, action)
|
||||
# return
|
||||
|
||||
# special case: iq-result stanza does not come with a jingle element
|
||||
if action == 'iq-result':
|
||||
for cn in self.contents.values():
|
||||
cn.on_stanza(stanza, None, error, action)
|
||||
return
|
||||
|
||||
for content in jingle.iterTags('content'):
|
||||
name = content['name']
|
||||
creator = content['creator']
|
||||
|
@ -483,9 +586,11 @@ class JingleSession(object):
|
|||
else:
|
||||
# TODO
|
||||
text = reason
|
||||
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=None,
|
||||
reason=text))
|
||||
|
||||
if reason == 'cancel' and self.session_type_FT:
|
||||
gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=None,
|
||||
reason=text))
|
||||
|
||||
def __broadcast_all(self, stanza, jingle, error, action):
|
||||
"""
|
||||
|
@ -502,6 +607,8 @@ class JingleSession(object):
|
|||
|
||||
for element in jingle.iterTags('content'):
|
||||
transport = get_jingle_transport(element.getTag('transport'))
|
||||
if transport:
|
||||
transport.ourjid = self.ourjid
|
||||
content_type = get_jingle_content(element.getTag('description'))
|
||||
if content_type:
|
||||
try:
|
||||
|
@ -531,12 +638,15 @@ class JingleSession(object):
|
|||
return (contents, contents_rejected, failure_reason)
|
||||
|
||||
def __dispatch_error(self, error=None, text=None, type_=None):
|
||||
|
||||
if text:
|
||||
text = '%s (%s)' % (error, text)
|
||||
if type_ != 'modify':
|
||||
gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self,
|
||||
reason=text or error))
|
||||
|
||||
|
||||
|
||||
def __reason_from_stanza(self, stanza):
|
||||
# TODO: Move to GUI?
|
||||
|
@ -557,13 +667,12 @@ class JingleSession(object):
|
|||
return (reason, text)
|
||||
|
||||
def __make_jingle(self, action, reason=None):
|
||||
stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
|
||||
stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid),
|
||||
frm=self.ourjid)
|
||||
attrs = {'action': action,
|
||||
'sid': self.sid}
|
||||
if action == 'session-initiate':
|
||||
attrs['initiator'] = self.initiator
|
||||
elif action == 'session-accept':
|
||||
attrs['responder'] = self.responder
|
||||
'sid': self.sid,
|
||||
'initiator' : self.initiator}
|
||||
|
||||
jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
|
||||
if reason is not None:
|
||||
jingle.addChild(node=reason)
|
||||
|
@ -605,6 +714,7 @@ class JingleSession(object):
|
|||
self.__append_contents(jingle)
|
||||
self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(stanza.getID())
|
||||
self.state = JingleStates.pending
|
||||
|
||||
def __session_accept(self):
|
||||
|
@ -613,6 +723,7 @@ class JingleSession(object):
|
|||
self.__append_contents(jingle)
|
||||
self.__broadcast(stanza, jingle, None, 'session-accept-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(stanza.getID())
|
||||
self.state = JingleStates.active
|
||||
|
||||
def __session_info(self, payload=None):
|
||||
|
@ -621,9 +732,15 @@ class JingleSession(object):
|
|||
if payload:
|
||||
jingle.addChild(node=payload)
|
||||
self.connection.connection.send(stanza)
|
||||
|
||||
def _JingleFileTransfer__session_info(self, p):
|
||||
# For some strange reason when I call
|
||||
# self.session.__session_info(h) from the jingleFileTransfer object
|
||||
# within a thread, this method gets called instead. Even though, it
|
||||
# isn't being called explicitly.
|
||||
self.__session_info(p)
|
||||
|
||||
def _session_terminate(self, reason=None):
|
||||
assert self.state != JingleStates.ended
|
||||
stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
|
||||
self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
|
||||
if self.connection.connection and self.connection.connected >= 2:
|
||||
|
@ -647,7 +764,8 @@ class JingleSession(object):
|
|||
stanza, jingle = self.__make_jingle('content-add')
|
||||
self.__append_content(jingle, content)
|
||||
self.__broadcast(stanza, jingle, None, 'content-add-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
id_ = self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(id_)
|
||||
|
||||
def __content_accept(self, content):
|
||||
# TODO: test
|
||||
|
@ -655,7 +773,8 @@ class JingleSession(object):
|
|||
stanza, jingle = self.__make_jingle('content-accept')
|
||||
self.__append_content(jingle, content)
|
||||
self.__broadcast(stanza, jingle, None, 'content-accept-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
id_ = self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(id_)
|
||||
|
||||
def __content_reject(self, content):
|
||||
assert self.state != JingleStates.ended
|
||||
|
|
|
@ -16,21 +16,29 @@ Handles Jingle Transports (currently only ICE-UDP)
|
|||
"""
|
||||
|
||||
import xmpp
|
||||
import socket
|
||||
from common import gajim
|
||||
from common.protocol.bytestream import ConnectionSocks5Bytestream
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('gajim.c.jingle_transport')
|
||||
|
||||
|
||||
transports = {}
|
||||
|
||||
def get_jingle_transport(node):
|
||||
namespace = node.getNamespace()
|
||||
if namespace in transports:
|
||||
return transports[namespace]()
|
||||
return transports[namespace](node)
|
||||
|
||||
|
||||
class TransportType(object):
|
||||
"""
|
||||
Possible types of a JingleTransport
|
||||
"""
|
||||
datagram = 1
|
||||
streaming = 2
|
||||
ICEUDP = 1
|
||||
SOCKS5 = 2
|
||||
IBB = 3
|
||||
|
||||
|
||||
class JingleTransport(object):
|
||||
|
@ -70,16 +78,258 @@ class JingleTransport(object):
|
|||
Return the list of transport candidates from a transport stanza
|
||||
"""
|
||||
return []
|
||||
|
||||
def set_connection(self, conn):
|
||||
self.connection = conn
|
||||
if not self.sid:
|
||||
self.sid = self.connection.connection.getAnID()
|
||||
|
||||
def set_file_props(self, file_props):
|
||||
self.file_props = file_props
|
||||
|
||||
def set_our_jid(self, jid):
|
||||
self.ourjid = jid
|
||||
|
||||
def set_sid(self, sid):
|
||||
self.sid = sid
|
||||
|
||||
class JingleTransportSocks5(JingleTransport):
|
||||
"""
|
||||
Socks5 transport in jingle scenario
|
||||
Note: Don't forget to call set_file_props after initialization
|
||||
"""
|
||||
def __init__(self, node=None):
|
||||
JingleTransport.__init__(self, TransportType.SOCKS5)
|
||||
self.connection = None
|
||||
self.remote_candidates = []
|
||||
self.sid = None
|
||||
if node and node.getAttr('sid'):
|
||||
self.sid = node.getAttr('sid')
|
||||
|
||||
|
||||
def make_candidate(self, candidate):
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
log.info('candidate dict, %s' % candidate)
|
||||
attrs = {
|
||||
'cid': candidate['candidate_id'],
|
||||
'host': candidate['host'],
|
||||
'jid': candidate['jid'],
|
||||
'port': candidate['port'],
|
||||
'priority': candidate['priority'],
|
||||
'type': candidate['type']
|
||||
}
|
||||
|
||||
return xmpp.Node('candidate', attrs=attrs)
|
||||
|
||||
def make_transport(self, candidates=None, add_candidates = True):
|
||||
if add_candidates:
|
||||
self._add_local_ips_as_candidates()
|
||||
self._add_additional_candidates()
|
||||
self._add_proxy_candidates()
|
||||
transport = JingleTransport.make_transport(self, candidates)
|
||||
else:
|
||||
transport = xmpp.Node('transport')
|
||||
transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM)
|
||||
transport.setAttr('sid', self.sid)
|
||||
return transport
|
||||
|
||||
def parse_transport_stanza(self, transport):
|
||||
candidates = []
|
||||
for candidate in transport.iterTags('candidate'):
|
||||
typ = 'direct' # default value
|
||||
if candidate.has_attr('type'):
|
||||
typ = candidate['type']
|
||||
cand = {
|
||||
'state': 0,
|
||||
'target': self.ourjid,
|
||||
'host': candidate['host'],
|
||||
'port': int(candidate['port']),
|
||||
'cid': candidate['cid'],
|
||||
'type': typ,
|
||||
'priority': candidate['priority']
|
||||
}
|
||||
candidates.append(cand)
|
||||
|
||||
# we need this when we construct file_props on session-initiation
|
||||
self.remote_candidates = candidates
|
||||
return candidates
|
||||
|
||||
|
||||
def _add_candidates(self, candidates):
|
||||
for cand in candidates:
|
||||
in_remote = False
|
||||
for cand2 in self.remote_candidates:
|
||||
if cand['host'] == cand2['host'] and \
|
||||
cand['port'] == cand2['port']:
|
||||
in_remote = True
|
||||
break
|
||||
if not in_remote:
|
||||
self.candidates.append(cand)
|
||||
|
||||
def _add_local_ips_as_candidates(self):
|
||||
if not self.connection:
|
||||
return
|
||||
local_ip_cand = []
|
||||
port = int(gajim.config.get('file_transfers_port'))
|
||||
type_preference = 126 #type preference of connection type. XEP-0260 section 2.2
|
||||
c = {'host': self.connection.peerhost[0]}
|
||||
c['candidate_id'] = self.connection.connection.getAnID()
|
||||
c['port'] = port
|
||||
c['type'] = 'direct'
|
||||
c['jid'] = self.ourjid
|
||||
c['priority'] = (2**16) * type_preference
|
||||
|
||||
local_ip_cand.append(c)
|
||||
|
||||
for addr in socket.getaddrinfo(socket.gethostname(), None):
|
||||
if not addr[4][0] in local_ip_cand and not addr[4][0].startswith('127'):
|
||||
c = {'host': addr[4][0]}
|
||||
c['candidate_id'] = self.connection.connection.getAnID()
|
||||
c['port'] = port
|
||||
c['type'] = 'direct'
|
||||
c['jid'] = self.ourjid
|
||||
c['priority'] = (2**16) * type_preference
|
||||
c['initiator'] = self.file_props['sender']
|
||||
c['target'] = self.file_props['receiver']
|
||||
local_ip_cand.append(c)
|
||||
|
||||
self._add_candidates(local_ip_cand)
|
||||
|
||||
def _add_additional_candidates(self):
|
||||
if not self.connection:
|
||||
return
|
||||
type_preference = 126
|
||||
additional_ip_cand = []
|
||||
port = int(gajim.config.get('file_transfers_port'))
|
||||
ft_add_hosts = gajim.config.get('ft_add_hosts_to_send')
|
||||
|
||||
if ft_add_hosts:
|
||||
hosts = [e.strip() for e in ft_add_hosts.split(',')]
|
||||
for h in hosts:
|
||||
c = {'host': h}
|
||||
c['candidate_id'] = self.connection.connection.getAnID()
|
||||
c['port'] = port
|
||||
c['type'] = 'direct'
|
||||
c['jid'] = self.ourjid
|
||||
c['priority'] = (2**16) * type_preference
|
||||
c['initiator'] = self.file_props['sender']
|
||||
c['target'] = self.file_props['receiver']
|
||||
additional_ip_cand.append(c)
|
||||
|
||||
self._add_candidates(additional_ip_cand)
|
||||
|
||||
def _add_proxy_candidates(self):
|
||||
if not self.connection:
|
||||
return
|
||||
type_preference = 10
|
||||
proxy_cand = []
|
||||
socks5conn = self.connection
|
||||
proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props)
|
||||
|
||||
if proxyhosts:
|
||||
self.file_props['proxyhosts'] = proxyhosts
|
||||
|
||||
for proxyhost in proxyhosts:
|
||||
c = {'host': proxyhost['host']}
|
||||
c['candidate_id'] = self.connection.connection.getAnID()
|
||||
c['port'] = int(proxyhost['port'])
|
||||
c['type'] = 'proxy'
|
||||
c['jid'] = proxyhost['jid']
|
||||
c['priority'] = (2**16) * type_preference
|
||||
c['initiator'] = self.file_props['sender']
|
||||
c['target'] = self.file_props['receiver']
|
||||
proxy_cand.append(c)
|
||||
|
||||
self._add_candidates(proxy_cand)
|
||||
|
||||
def get_content(self):
|
||||
sesn = self.connection.get_jingle_session(self.ourjid,
|
||||
self.file_props['session-sid'])
|
||||
for content in sesn.contents.values():
|
||||
if content.transport == self:
|
||||
return content
|
||||
|
||||
def _on_proxy_auth_ok(self, proxy):
|
||||
log.info('proxy auth ok for ' + str(proxy))
|
||||
# send activate request to proxy, send activated confirmation to peer
|
||||
if not self.connection:
|
||||
return
|
||||
sesn = self.connection.get_jingle_session(self.ourjid,
|
||||
self.file_props['session-sid'])
|
||||
if sesn is None:
|
||||
return
|
||||
|
||||
iq = xmpp.Iq(to=proxy['jid'], frm=self.ourjid, typ='set')
|
||||
auth_id = "au_" + proxy['sid']
|
||||
iq.setID(auth_id)
|
||||
query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
|
||||
query.setAttr('sid', proxy['sid'])
|
||||
activate = query.setTag('activate')
|
||||
activate.setData(sesn.peerjid)
|
||||
iq.setID(auth_id)
|
||||
self.connection.connection.send(iq)
|
||||
|
||||
|
||||
content = xmpp.Node('content')
|
||||
content.setAttr('creator', 'initiator')
|
||||
c = self.get_content()
|
||||
content.setAttr('name', c.name)
|
||||
transport = xmpp.Node('transport')
|
||||
transport.setNamespace(xmpp.NS_JINGLE_BYTESTREAM)
|
||||
transport.setAttr('sid', proxy['sid'])
|
||||
activated = xmpp.Node('activated')
|
||||
cid = None
|
||||
|
||||
if 'cid' in proxy:
|
||||
cid = proxy['cid']
|
||||
else:
|
||||
for host in self.candidates:
|
||||
if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \
|
||||
and host['port'] == proxy['port']:
|
||||
cid = host['candidate_id']
|
||||
break
|
||||
if cid is None:
|
||||
raise Exception, 'cid is missing'
|
||||
activated.setAttr('cid', cid)
|
||||
transport.addChild(node=activated)
|
||||
content.addChild(node=transport)
|
||||
sesn.send_transport_info(content)
|
||||
|
||||
|
||||
class JingleTransportIBB(JingleTransport):
|
||||
|
||||
def __init__(self, node=None, block_sz=None):
|
||||
|
||||
JingleTransport.__init__(self, TransportType.IBB)
|
||||
|
||||
if block_sz:
|
||||
self.block_sz = block_sz
|
||||
else:
|
||||
self.block_sz = '4096'
|
||||
|
||||
self.connection = None
|
||||
self.sid = None
|
||||
if node and node.getAttr('sid'):
|
||||
self.sid = node.getAttr('sid')
|
||||
|
||||
|
||||
def make_transport(self):
|
||||
|
||||
transport = xmpp.Node('transport')
|
||||
transport.setNamespace(xmpp.NS_JINGLE_IBB)
|
||||
transport.setAttr('block-size', self.block_sz)
|
||||
transport.setAttr('sid', self.sid)
|
||||
return transport
|
||||
|
||||
try:
|
||||
import farstream
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
class JingleTransportICEUDP(JingleTransport):
|
||||
def __init__(self):
|
||||
JingleTransport.__init__(self, TransportType.datagram)
|
||||
def __init__(self, node):
|
||||
JingleTransport.__init__(self, TransportType.ICEUDP)
|
||||
|
||||
def make_candidate(self, candidate):
|
||||
types = {farstream.CANDIDATE_TYPE_HOST: 'host',
|
||||
|
@ -149,3 +399,5 @@ class JingleTransportICEUDP(JingleTransport):
|
|||
return candidates
|
||||
|
||||
transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP
|
||||
transports[xmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5
|
||||
transports[xmpp.NS_JINGLE_IBB] = JingleTransportIBB
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/common/jingle_xtls.py
|
||||
##
|
||||
## 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/>.
|
||||
##
|
||||
|
||||
import os
|
||||
|
||||
import logging
|
||||
import common
|
||||
from common import gajim
|
||||
log = logging.getLogger('gajim.c.jingle_xtls')
|
||||
|
||||
PYOPENSSL_PRESENT = False
|
||||
|
||||
pending_contents = {} # key-exchange id -> session, accept that session once key-exchange completes
|
||||
|
||||
def key_exchange_pend(id_, content):
|
||||
pending_contents[id_] = content
|
||||
|
||||
def approve_pending_content(id_):
|
||||
content = pending_contents[id_]
|
||||
content.session.approve_session()
|
||||
content.session.approve_content('file', name=content.name)
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
PYOPENSSL_PRESENT = True
|
||||
except ImportError:
|
||||
log.info("PyOpenSSL not available")
|
||||
|
||||
if PYOPENSSL_PRESENT:
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL.SSL import Context
|
||||
from OpenSSL import crypto
|
||||
TYPE_RSA = crypto.TYPE_RSA
|
||||
TYPE_DSA = crypto.TYPE_DSA
|
||||
|
||||
SELF_SIGNED_CERTIFICATE = 'localcert'
|
||||
|
||||
def default_callback(connection, certificate, error_num, depth, return_code):
|
||||
log.info("certificate: %s" % certificate)
|
||||
return return_code
|
||||
|
||||
def load_cert_file(cert_path, cert_store):
|
||||
"""
|
||||
This is almost identical to the one in common.xmpp.tls_nb
|
||||
"""
|
||||
if not os.path.isfile(cert_path):
|
||||
return
|
||||
try:
|
||||
f = open(cert_path)
|
||||
except IOError, e:
|
||||
log.warning('Unable to open certificate file %s: %s' % \
|
||||
(cert_path, str(e)))
|
||||
return
|
||||
lines = f.readlines()
|
||||
i = 0
|
||||
begin = -1
|
||||
for line in lines:
|
||||
if 'BEGIN CERTIFICATE' in line:
|
||||
begin = i
|
||||
elif 'END CERTIFICATE' in line and begin > -1:
|
||||
cert = ''.join(lines[begin:i+2])
|
||||
try:
|
||||
x509cert = OpenSSL.crypto.load_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||
cert_store.add_cert(x509cert)
|
||||
except OpenSSL.crypto.Error, exception_obj:
|
||||
log.warning('Unable to load a certificate from file %s: %s' %\
|
||||
(cert_path, exception_obj.args[0][0][2]))
|
||||
except:
|
||||
log.warning('Unknown error while loading certificate from file '
|
||||
'%s' % cert_path)
|
||||
begin = -1
|
||||
i += 1
|
||||
|
||||
def get_context(fingerprint, verify_cb=None):
|
||||
"""
|
||||
constructs and returns the context objects
|
||||
"""
|
||||
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
||||
|
||||
if fingerprint == 'server': # for testing purposes only
|
||||
ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb or default_callback)
|
||||
elif fingerprint == 'client':
|
||||
ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback)
|
||||
|
||||
cert_name = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE)
|
||||
ctx.use_privatekey_file (cert_name + '.pkey')
|
||||
ctx.use_certificate_file(cert_name + '.cert')
|
||||
store = ctx.get_cert_store()
|
||||
for f in os.listdir(os.path.expanduser(gajim.MY_PEER_CERTS_PATH)):
|
||||
load_cert_file(os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), f), store)
|
||||
log.debug('certificate file ' + f + ' loaded fingerprint ' + \
|
||||
fingerprint)
|
||||
return ctx
|
||||
|
||||
def send_cert(con, jid_from, sid):
|
||||
certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + '.cert'
|
||||
certfile = open(certpath, 'r')
|
||||
certificate = ''
|
||||
for line in certfile.readlines():
|
||||
if not line.startswith('-'):
|
||||
certificate += line
|
||||
iq = common.xmpp.Iq('result', to=jid_from);
|
||||
iq.setAttr('id', sid)
|
||||
|
||||
pubkey = iq.setTag('pubkeys')
|
||||
pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY)
|
||||
|
||||
keyinfo = pubkey.setTag('keyinfo')
|
||||
name = keyinfo.setTag('name')
|
||||
name.setData('CertificateHash')
|
||||
cert = keyinfo.setTag('x509cert')
|
||||
cert.setData(certificate)
|
||||
|
||||
con.send(iq)
|
||||
|
||||
def handle_new_cert(con, obj, jid_from):
|
||||
jid = gajim.get_jid_without_resource(jid_from)
|
||||
certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid)
|
||||
certpath += '.cert'
|
||||
|
||||
id_ = obj.getAttr('id')
|
||||
|
||||
x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert')
|
||||
|
||||
cert = x509cert.getData()
|
||||
|
||||
f = open(certpath, 'w')
|
||||
f.write('-----BEGIN CERTIFICATE-----\n')
|
||||
f.write(cert)
|
||||
f.write('-----END CERTIFICATE-----\n')
|
||||
|
||||
approve_pending_content(id_)
|
||||
|
||||
def send_cert_request(con, to_jid):
|
||||
iq = common.xmpp.Iq('get', to=to_jid)
|
||||
id_ = con.connection.getAnID()
|
||||
iq.setAttr('id', id_)
|
||||
pubkey = iq.setTag('pubkeys')
|
||||
pubkey.setNamespace(common.xmpp.NS_PUBKEY_PUBKEY)
|
||||
con.connection.send(iq)
|
||||
return unicode(id_)
|
||||
|
||||
# the following code is partly due to pyopenssl examples
|
||||
|
||||
def createKeyPair(type, bits):
|
||||
"""
|
||||
Create a public/private key pair.
|
||||
|
||||
Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA
|
||||
bits - Number of bits to use in the key
|
||||
Returns: The public/private key pair in a PKey object
|
||||
"""
|
||||
pkey = crypto.PKey()
|
||||
pkey.generate_key(type, bits)
|
||||
return pkey
|
||||
|
||||
def createCertRequest(pkey, digest="md5", **name):
|
||||
"""
|
||||
Create a certificate request.
|
||||
|
||||
Arguments: pkey - The key to associate with the request
|
||||
digest - Digestion method to use for signing, default is md5
|
||||
**name - The name of the subject of the request, possible
|
||||
arguments are:
|
||||
C - Country name
|
||||
ST - State or province name
|
||||
L - Locality name
|
||||
O - Organization name
|
||||
OU - Organizational unit name
|
||||
CN - Common name
|
||||
emailAddress - E-mail address
|
||||
Returns: The certificate request in an X509Req object
|
||||
"""
|
||||
req = crypto.X509Req()
|
||||
subj = req.get_subject()
|
||||
|
||||
for (key,value) in name.items():
|
||||
setattr(subj, key, value)
|
||||
|
||||
req.set_pubkey(pkey)
|
||||
req.sign(pkey, digest)
|
||||
return req
|
||||
|
||||
def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="md5"):
|
||||
"""
|
||||
Generate a certificate given a certificate request.
|
||||
|
||||
Arguments: req - Certificate reqeust to use
|
||||
issuerCert - The certificate of the issuer
|
||||
issuerKey - The private key of the issuer
|
||||
serial - Serial number for the certificate
|
||||
notBefore - Timestamp (relative to now) when the certificate
|
||||
starts being valid
|
||||
notAfter - Timestamp (relative to now) when the certificate
|
||||
stops being valid
|
||||
digest - Digest method to use for signing, default is md5
|
||||
Returns: The signed certificate in an X509 object
|
||||
"""
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(serial)
|
||||
cert.gmtime_adj_notBefore(notBefore)
|
||||
cert.gmtime_adj_notAfter(notAfter)
|
||||
cert.set_issuer(issuerCert.get_subject())
|
||||
cert.set_subject(req.get_subject())
|
||||
cert.set_pubkey(req.get_pubkey())
|
||||
cert.sign(issuerKey, digest)
|
||||
return cert
|
||||
|
||||
def make_certs(filepath, CN):
|
||||
"""
|
||||
make self signed certificates
|
||||
filepath : absolute path of certificate file, will be appended the '.pkey' and '.cert' extensions
|
||||
CN : common name
|
||||
"""
|
||||
key = createKeyPair(TYPE_RSA, 1024)
|
||||
req = createCertRequest(key, CN=CN)
|
||||
cert = createCertificate(req, (req, key), 0, (0, 60*60*24*365*5)) # five years
|
||||
open(filepath + '.pkey', 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
|
||||
open(filepath + '.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_certs('./selfcert', 'gajim')
|
|
@ -37,9 +37,8 @@ from common import xmpp
|
|||
from common import gajim
|
||||
from common import helpers
|
||||
from common import dataforms
|
||||
from common.connection_handlers_events import FileRequestReceivedEvent, \
|
||||
FileRequestErrorEvent, InformationEvent
|
||||
from common import ged
|
||||
from common import jingle_xtls
|
||||
|
||||
from common.socks5 import Socks5Receiver
|
||||
|
||||
|
@ -140,6 +139,34 @@ class ConnectionBytestream:
|
|||
# user response to ConfirmationDialog may come after we've disconneted
|
||||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
|
||||
# file transfer initiated by a jingle session
|
||||
log.info("send_file_approval: jingle session accept")
|
||||
if file_props.get('session-type') == 'jingle':
|
||||
session = self.get_jingle_session(file_props['sender'],
|
||||
file_props['session-sid'])
|
||||
if not session:
|
||||
return
|
||||
content = None
|
||||
for c in session.contents.values():
|
||||
if c.transport.sid == file_props['sid']:
|
||||
content = c
|
||||
break
|
||||
if not content:
|
||||
return
|
||||
gajim.socks5queue.add_file_props(self.name, file_props)
|
||||
|
||||
if not session.accepted:
|
||||
if session.get_content('file', content.name).use_security:
|
||||
id_ = jingle_xtls.send_cert_request(self,
|
||||
file_props['sender'])
|
||||
jingle_xtls.key_exchange_pend(id_, content)
|
||||
return
|
||||
session.approve_session()
|
||||
|
||||
session.approve_content('file', content.name)
|
||||
return
|
||||
|
||||
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
|
||||
iq.setAttr('id', file_props['request-id'])
|
||||
si = iq.setTag('si', namespace=xmpp.NS_SI)
|
||||
|
@ -168,6 +195,10 @@ class ConnectionBytestream:
|
|||
# user response to ConfirmationDialog may come after we've disconneted
|
||||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
if file_props['session-type'] == 'jingle':
|
||||
jingle = self._sessions[file_props['session-sid']]
|
||||
jingle.cancel_session()
|
||||
return
|
||||
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error')
|
||||
iq.setAttr('id', file_props['request-id'])
|
||||
if code == '400' and typ in ('stream', 'profile'):
|
||||
|
@ -221,6 +252,7 @@ class ConnectionBytestream:
|
|||
raise xmpp.NodeProcessed
|
||||
|
||||
def _siSetCB(self, con, iq_obj):
|
||||
from common.connection_handlers_events import FileRequestReceivedEvent
|
||||
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self,
|
||||
stanza=iq_obj))
|
||||
raise xmpp.NodeProcessed
|
||||
|
@ -240,6 +272,7 @@ class ConnectionBytestream:
|
|||
return
|
||||
jid = self._ft_get_from(iq_obj)
|
||||
file_props['error'] = -3
|
||||
from common.connection_handlers_events import FileRequestErrorEvent
|
||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
||||
jid=jid, file_props=file_props, error_msg=''))
|
||||
raise xmpp.NodeProcessed
|
||||
|
@ -273,6 +306,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
|||
if contact.get_full_jid() == receiver_jid:
|
||||
file_props['error'] = -5
|
||||
self.remove_transfer(file_props)
|
||||
from common.connection_handlers_events import \
|
||||
FileRequestErrorEvent
|
||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
|
||||
conn=self, jid=contact.jid, file_props=file_props,
|
||||
error_msg=''))
|
||||
|
@ -332,9 +367,10 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
|||
|
||||
port = gajim.config.get('file_transfers_port')
|
||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||
self._result_socks5_sid, file_props['sid'])
|
||||
self._result_socks5_sid, file_props)
|
||||
if not listener:
|
||||
file_props['error'] = -5
|
||||
from common.connection_handlers_events import FileRequestErrorEvent
|
||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
||||
jid=unicode(receiver), file_props=file_props, error_msg=''))
|
||||
self._connect_error(unicode(receiver), file_props['sid'],
|
||||
|
@ -374,6 +410,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
|||
port = gajim.config.get('file_transfers_port')
|
||||
self._add_streamhosts_to_query(query, sender, port, my_ips)
|
||||
except socket.gaierror:
|
||||
from common.connection_handlers_events import InformationEvent
|
||||
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
|
||||
level='error', pri_txt=_('Wrong host'),
|
||||
sec_txt=_('Invalid local address? :-O')))
|
||||
|
@ -546,6 +583,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
|||
if file_props is not None:
|
||||
self.disconnect_transfer(file_props)
|
||||
file_props['error'] = -3
|
||||
from common.connection_handlers_events import \
|
||||
FileRequestErrorEvent
|
||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
|
||||
conn=self, jid=to, file_props=file_props, error_msg=msg))
|
||||
|
||||
|
@ -578,6 +617,7 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
|||
return
|
||||
file_props = self.files_props[id_]
|
||||
file_props['error'] = -4
|
||||
from common.connection_handlers_events import FileRequestErrorEvent
|
||||
gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
|
||||
jid=jid, file_props=file_props, error_msg=''))
|
||||
raise xmpp.NodeProcessed
|
||||
|
@ -713,7 +753,10 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
|
|||
raise xmpp.NodeProcessed
|
||||
|
||||
else:
|
||||
gajim.socks5queue.send_file(file_props, self.name)
|
||||
if 'stopped' in file_props and file_props['stopped']:
|
||||
self.remove_transfer(file_props)
|
||||
else:
|
||||
gajim.socks5queue.send_file(file_props, self.name, 'client')
|
||||
if 'fast' in file_props:
|
||||
fasts = file_props['fast']
|
||||
if len(fasts) > 0:
|
||||
|
@ -741,9 +784,9 @@ class ConnectionIBBytestream(ConnectionBytestream):
|
|||
elif typ == 'set' and stanza.getTag('close', namespace=xmpp.NS_IBB):
|
||||
self.StreamCloseHandler(conn, stanza)
|
||||
elif typ == 'result':
|
||||
self.StreamCommitHandler(conn, stanza)
|
||||
self.SendHandler()
|
||||
elif typ == 'error':
|
||||
self.StreamOpenReplyHandler(conn, stanza)
|
||||
gajim.socks5queue.error_cb()
|
||||
else:
|
||||
conn.send(xmpp.Error(stanza, xmpp.ERR_BAD_REQUEST))
|
||||
raise xmpp.NodeProcessed
|
||||
|
@ -918,13 +961,17 @@ class ConnectionIBBytestream(ConnectionBytestream):
|
|||
log.debug('StreamCloseHandler called sid->%s' % sid)
|
||||
# look in sending files
|
||||
if sid in self.files_props.keys():
|
||||
conn.send(stanza.buildReply('result'))
|
||||
gajim.socks5queue.complete_transfer_cb(self.name, file_props)
|
||||
reply = stanza.buildReply('result')
|
||||
reply.delChild('close')
|
||||
conn.send(reply)
|
||||
gajim.socks5queue.complete_transfer_cb(self.name, self.files_props[sid])
|
||||
del self.files_props[sid]
|
||||
# look in receiving files
|
||||
elif gajim.socks5queue.get_file_props(self.name, sid):
|
||||
file_props = gajim.socks5queue.get_file_props(self.name, sid)
|
||||
conn.send(stanza.buildReply('result'))
|
||||
reply = stanza.buildReply('result')
|
||||
reply.delChild('close')
|
||||
conn.send(reply)
|
||||
file_props['fp'].close()
|
||||
gajim.socks5queue.complete_transfer_cb(self.name, file_props)
|
||||
gajim.socks5queue.remove_file_props(self.name, sid)
|
||||
|
@ -964,6 +1011,7 @@ class ConnectionIBBytestream(ConnectionBytestream):
|
|||
if stanza.getTag('data'):
|
||||
if self.IBBMessageHandler(conn, stanza):
|
||||
reply = stanza.buildReply('result')
|
||||
reply.delChild('data')
|
||||
conn.send(reply)
|
||||
raise xmpp.NodeProcessed
|
||||
elif syn_id == self.last_sent_ibb_id:
|
||||
|
|
1258
src/common/socks5.py
1258
src/common/socks5.py
File diff suppressed because it is too large
Load Diff
|
@ -23,6 +23,7 @@ sub- stanzas) handling routines
|
|||
from simplexml import Node, NodeBuilder
|
||||
import time
|
||||
import string
|
||||
import hashlib
|
||||
|
||||
def ascii_upper(s):
|
||||
trans_table = string.maketrans(string.ascii_lowercase,
|
||||
|
@ -90,8 +91,12 @@ NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166
|
|||
NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167
|
||||
NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167
|
||||
NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167
|
||||
NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:3' # XEP-0234
|
||||
NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle
|
||||
NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177
|
||||
NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176
|
||||
NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260
|
||||
NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' # XEP-0261
|
||||
NS_LAST = 'jabber:iq:last'
|
||||
NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080
|
||||
NS_MESSAGE = 'message' # Jabberd2
|
||||
|
@ -153,8 +158,16 @@ NS_DATA_LAYOUT = 'http://jabber.org/protocol/xdata-layout' # XEP-0141
|
|||
NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate' # XEP-0122
|
||||
NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
|
||||
NS_RECEIPTS = 'urn:xmpp:receipts'
|
||||
NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2' # XEP-0189
|
||||
NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2'
|
||||
NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2'
|
||||
NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198
|
||||
|
||||
NS_HASHES = 'urn:xmpp:hashes:0' # XEP-300
|
||||
NS_HASHES_MD5 = 'urn:xmpp:hash-function-textual-names:md5'
|
||||
NS_HASHES_SHA1 = 'urn:xmpp:hash-function-textual-names:sha-1'
|
||||
NS_HASHES_SHA256 = 'urn:xmpp:hash-function-textual-names:sha-256'
|
||||
NS_HASHES_SHA512 = 'urn:xmpp:hash-function-textual-names:sha-512'
|
||||
|
||||
xmpp_stream_error_conditions = '''
|
||||
bad-format -- -- -- The entity has sent XML that cannot be processed.
|
||||
bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.
|
||||
|
@ -1024,13 +1037,84 @@ class Iq(Protocol):
|
|||
attrs={'id': self.getID()})
|
||||
iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
|
||||
return iq
|
||||
|
||||
|
||||
class Hashes(Node):
|
||||
"""
|
||||
Hash elements for various XEPs as defined in XEP-300
|
||||
"""
|
||||
|
||||
"""
|
||||
RECOMENDED HASH USE:
|
||||
Algorithm Support
|
||||
MD2 MUST NOT
|
||||
MD4 MUST NOT
|
||||
MD5 MAY
|
||||
SHA-1 MUST
|
||||
SHA-256 MUST
|
||||
SHA-512 SHOULD
|
||||
"""
|
||||
|
||||
supported = ('md5', 'sha-1', 'sha-256', 'sha-512')
|
||||
|
||||
def __init__(self, nsp=NS_HASHES):
|
||||
Node.__init__(self, None, {}, [], None, None,False, None)
|
||||
self.setNamespace(nsp)
|
||||
self.setName('hashes')
|
||||
|
||||
def calculateHash(self, algo, file_string):
|
||||
"""
|
||||
Calculate the hash and add it. It is preferable doing it here
|
||||
instead of doing it all over the place in Gajim.
|
||||
"""
|
||||
hl = None
|
||||
hash_ = None
|
||||
# file_string can be a string or a file
|
||||
if type(file_string) == str: # if it is a string
|
||||
if algo == 'md5':
|
||||
hl = hashlib.md5()
|
||||
elif algo == 'sha-1':
|
||||
hl = hashlib.sha1()
|
||||
elif algo == 'sha-256':
|
||||
hl = hashlib.sha256()
|
||||
elif algo == 'sha-512':
|
||||
hl = hashlib.sha512()
|
||||
|
||||
if hl:
|
||||
hl.update(file_string)
|
||||
hash_ = hl.hexdigest()
|
||||
else: # if it is a file
|
||||
|
||||
if algo == 'md5':
|
||||
hl = hashlib.md5()
|
||||
elif algo == 'sha-1':
|
||||
hl = hashlib.sha1()
|
||||
elif algo == 'sha-256':
|
||||
hl = hashlib.sha256()
|
||||
elif algo == 'sha-512':
|
||||
hl = hashlib.sha512()
|
||||
|
||||
if hl:
|
||||
for line in file_string:
|
||||
hl.update(line)
|
||||
hash_ = hl.hexdigest()
|
||||
|
||||
return hash_
|
||||
|
||||
def addHash(self, hash_, algo):
|
||||
"""
|
||||
More than one hash can be added. Although it is permitted, it should
|
||||
not be done for big files because it could slow down Gajim.
|
||||
"""
|
||||
attrs = {}
|
||||
attrs['algo'] = algo
|
||||
self.addChild('hash', attrs, [hash_])
|
||||
|
||||
class Acks(Node):
|
||||
"""
|
||||
Acknowledgement elements for Stream Management
|
||||
"""
|
||||
def __init__(self, nsp=NS_STREAM_MGMT):
|
||||
Node.__init__(self, None, {}, [], None, None,False, None)
|
||||
Node.__init__(self, None, {}, [], None, None, False, None)
|
||||
self.setNamespace(nsp)
|
||||
|
||||
def buildAnswer(self, handled):
|
||||
|
@ -1407,3 +1491,4 @@ class DataForm(Node):
|
|||
Simple dictionary interface for setting datafields values by their names
|
||||
"""
|
||||
return self.setField(name).setValue(val)
|
||||
|
||||
|
|
|
@ -1615,16 +1615,15 @@ class YesNoDialog(HigDialog):
|
|||
"""
|
||||
|
||||
def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
|
||||
on_response_no=None):
|
||||
on_response_no=None, type_=gtk.MESSAGE_QUESTION):
|
||||
self.user_response_yes = on_response_yes
|
||||
self.user_response_no = on_response_no
|
||||
if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
|
||||
parent = gajim.interface.roster.window
|
||||
else:
|
||||
parent = None
|
||||
HigDialog.__init__(self, parent, gtk.MESSAGE_QUESTION,
|
||||
gtk.BUTTONS_YES_NO, pritext, sectext,
|
||||
on_response_yes=self.on_response_yes,
|
||||
HigDialog.__init__(self, parent, type_, gtk.BUTTONS_YES_NO, pritext,
|
||||
sectext, on_response_yes=self.on_response_yes,
|
||||
on_response_no=self.on_response_no)
|
||||
|
||||
if checktext:
|
||||
|
|
|
@ -35,6 +35,10 @@ from common import gajim
|
|||
from common import helpers
|
||||
from common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
|
||||
is_transfer_stopped)
|
||||
from common.xmpp.protocol import NS_JINGLE_FILE_TRANSFER
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('gajim.filetransfer_window')
|
||||
|
||||
C_IMAGE = 0
|
||||
C_LABELS = 1
|
||||
|
@ -42,7 +46,8 @@ C_FILE = 2
|
|||
C_TIME = 3
|
||||
C_PROGRESS = 4
|
||||
C_PERCENT = 5
|
||||
C_SID = 6
|
||||
C_PULSE = 6
|
||||
C_SID = 7
|
||||
|
||||
|
||||
class FileTransfersWindow:
|
||||
|
@ -61,7 +66,8 @@ class FileTransfersWindow:
|
|||
shall_notify = gajim.config.get('notify_on_file_complete')
|
||||
self.notify_ft_checkbox.set_active(shall_notify
|
||||
)
|
||||
self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str)
|
||||
self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int,
|
||||
int, str)
|
||||
self.tree.set_model(self.model)
|
||||
col = gtk.TreeViewColumn()
|
||||
|
||||
|
@ -108,6 +114,7 @@ class FileTransfersWindow:
|
|||
col.pack_start(renderer, expand=False)
|
||||
col.add_attribute(renderer, 'text', C_PROGRESS)
|
||||
col.add_attribute(renderer, 'value', C_PERCENT)
|
||||
col.add_attribute(renderer, 'pulse', C_PULSE)
|
||||
col.set_resizable(True)
|
||||
col.set_expand(False)
|
||||
self.tree.append_column(col)
|
||||
|
@ -121,6 +128,8 @@ class FileTransfersWindow:
|
|||
'pause': gtk.STOCK_MEDIA_PAUSE,
|
||||
'continue': gtk.STOCK_MEDIA_PLAY,
|
||||
'ok': gtk.STOCK_APPLY,
|
||||
'computing': gtk.STOCK_EXECUTE,
|
||||
'hash_error': gtk.STOCK_STOP,
|
||||
}
|
||||
|
||||
self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
|
||||
|
@ -240,6 +249,21 @@ class FileTransfersWindow:
|
|||
dialogs.ErrorDialog(_('File transfer stopped'), sectext)
|
||||
self.tree.get_selection().unselect_all()
|
||||
|
||||
def show_hash_error(self, jid, file_props):
|
||||
def on_yes(dummy):
|
||||
# TODO: Request the file to the sender
|
||||
pass
|
||||
|
||||
if file_props['type'] == 'r':
|
||||
file_name = os.path.basename(file_props['file-name'])
|
||||
else:
|
||||
file_name = file_props['name']
|
||||
dialogs.YesNoDialog(('File transfer error'),
|
||||
_('The file %(file)s has been fully received, but it seems to be '
|
||||
'wrongly received.\nDo you want to reload it?') % \
|
||||
{'file': file_name}, on_response_yes=on_yes,
|
||||
type_=gtk.MESSAGE_ERROR)
|
||||
|
||||
def show_file_send_request(self, account, contact):
|
||||
win = gtk.ScrolledWindow()
|
||||
win.set_shadow_type(gtk.SHADOW_IN)
|
||||
|
@ -310,8 +334,17 @@ class FileTransfersWindow:
|
|||
file_path, file_name, file_desc)
|
||||
if file_props is None:
|
||||
return False
|
||||
self.add_transfer(account, contact, file_props)
|
||||
gajim.connections[account].send_file_request(file_props)
|
||||
if contact.supports(NS_JINGLE_FILE_TRANSFER):
|
||||
log.info("contact %s supports jingle file transfer"%(contact.get_full_jid()))
|
||||
# this call has the side effect of setting file_props['sid'] to the jingle sid, but for the sake of clarity
|
||||
# make it explicit here
|
||||
sid = gajim.connections[account].start_file_transfer(contact.get_full_jid(), file_props)
|
||||
file_props['sid'] = sid
|
||||
self.add_transfer(account, contact, file_props)
|
||||
else:
|
||||
log.info("contact does not support jingle file transfer")
|
||||
gajim.connections[account].send_file_request(file_props)
|
||||
self.add_transfer(account, contact, file_props)
|
||||
return True
|
||||
|
||||
def _start_receive(self, file_path, account, contact, file_props):
|
||||
|
@ -436,6 +469,36 @@ class FileTransfersWindow:
|
|||
file_props['stopped'] = True
|
||||
elif status == 'ok':
|
||||
file_props['completed'] = True
|
||||
text = self._format_percent(100)
|
||||
received_size = int(file_props['received-len'])
|
||||
full_size = int(file_props['size'])
|
||||
text += helpers.convert_bytes(received_size) + '/' + \
|
||||
helpers.convert_bytes(full_size)
|
||||
self.model.set(iter_, C_PROGRESS, text)
|
||||
self.model.set(iter_, C_PULSE, gobject.constants.G_MAXINT)
|
||||
elif status == 'computing':
|
||||
self.model.set(iter_, C_PULSE, 1)
|
||||
text = _('Checking file...') + '\n'
|
||||
received_size = int(file_props['received-len'])
|
||||
full_size = int(file_props['size'])
|
||||
text += helpers.convert_bytes(received_size) + '/' + \
|
||||
helpers.convert_bytes(full_size)
|
||||
self.model.set(iter_, C_PROGRESS, text)
|
||||
def pulse():
|
||||
p = self.model.get(iter_, C_PULSE)[0]
|
||||
if p == gobject.constants.G_MAXINT:
|
||||
return False
|
||||
self.model.set(iter_, C_PULSE, p + 1)
|
||||
return True
|
||||
gobject.timeout_add(100, pulse)
|
||||
elif status == 'hash_error':
|
||||
text = _('File error') + '\n'
|
||||
received_size = int(file_props['received-len'])
|
||||
full_size = int(file_props['size'])
|
||||
text += helpers.convert_bytes(received_size) + '/' + \
|
||||
helpers.convert_bytes(full_size)
|
||||
self.model.set(iter_, C_PROGRESS, text)
|
||||
self.model.set(iter_, C_PULSE, gobject.constants.G_MAXINT)
|
||||
self.model.set(iter_, C_IMAGE, self.get_icon(status))
|
||||
path = self.model.get_path(iter_)
|
||||
self.select_func(path)
|
||||
|
@ -576,7 +639,12 @@ class FileTransfersWindow:
|
|||
status = 'stop'
|
||||
self.model.set(iter_, 0, self.get_icon(status))
|
||||
if transfered_size == full_size:
|
||||
self.set_status(typ, sid, 'ok')
|
||||
# If we are receiver and this is a jingle session
|
||||
if file_props['type'] == 'r' and 'session-sid' in file_props:
|
||||
# Show that we are computing the hash
|
||||
self.set_status(typ, sid, 'computing')
|
||||
else:
|
||||
self.set_status(typ, sid, 'ok')
|
||||
elif just_began:
|
||||
path = self.model.get_path(iter_)
|
||||
self.select_func(path)
|
||||
|
@ -642,7 +710,7 @@ class FileTransfersWindow:
|
|||
file_name = file_props['name']
|
||||
text_props = gobject.markup_escape_text(file_name) + '\n'
|
||||
text_props += contact.get_shown_name()
|
||||
self.model.set(iter_, 1, text_labels, 2, text_props, C_SID,
|
||||
self.model.set(iter_, 1, text_labels, 2, text_props, C_PULSE, -1, C_SID,
|
||||
file_props['type'] + file_props['sid'])
|
||||
self.set_progress(file_props['type'], file_props['sid'], 0, iter_)
|
||||
if 'started' in file_props and file_props['started'] is False:
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
@ -71,6 +70,7 @@ from session import ChatControlSession
|
|||
import common.sleepy
|
||||
|
||||
from common.xmpp import idlequeue
|
||||
from common.xmpp import Hashes
|
||||
from common.zeroconf import connection_zeroconf
|
||||
from common import resolver
|
||||
from common import caps_cache
|
||||
|
@ -83,6 +83,7 @@ from common import logging_helpers
|
|||
from common.connection_handlers_events import OurShowEvent, \
|
||||
FileRequestErrorEvent, InformationEvent
|
||||
from common.connection import Connection
|
||||
from common import jingle
|
||||
|
||||
import roster_window
|
||||
import profile_window
|
||||
|
@ -918,21 +919,65 @@ class Interface:
|
|||
self.instances['file_transfers'].set_progress(file_props['type'],
|
||||
file_props['sid'], file_props['received-len'])
|
||||
|
||||
def __compare_hashes(self, account, file_props):
|
||||
session = gajim.connections[account].get_jingle_session(jid=None,
|
||||
sid=file_props['session-sid'])
|
||||
ft_win = self.instances['file_transfers']
|
||||
if not session.file_hash:
|
||||
# We disn't get the hash, sender probably don't support that
|
||||
jid = unicode(file_props['sender'])
|
||||
self.popup_ft_result(account, jid, file_props)
|
||||
ft_win.set_status(file_props['type'], file_props['sid'], 'ok')
|
||||
h = Hashes()
|
||||
try:
|
||||
file_ = open(file_props['file-name'], 'r')
|
||||
except:
|
||||
return
|
||||
hash_ = h.calculateHash(session.hash_algo, file_)
|
||||
file_.close()
|
||||
# If the hash we received and the hash of the file are the same,
|
||||
# then the file is not corrupt
|
||||
jid = unicode(file_props['sender'])
|
||||
if session.file_hash == hash_:
|
||||
self.popup_ft_result(account, jid, file_props)
|
||||
ft_win.set_status(file_props['type'], file_props['sid'], 'ok')
|
||||
else:
|
||||
# wrong hash, we need to get the file again!
|
||||
file_props['error'] = -10
|
||||
self.popup_ft_result(account, jid, file_props)
|
||||
ft_win.set_status(file_props['type'], file_props['sid'],
|
||||
'hash_error')
|
||||
# End jingle session
|
||||
if session:
|
||||
session.end_session()
|
||||
|
||||
def handle_event_file_rcv_completed(self, account, file_props):
|
||||
ft = self.instances['file_transfers']
|
||||
if file_props['error'] == 0:
|
||||
ft.set_progress(file_props['type'], file_props['sid'],
|
||||
file_props['received-len'])
|
||||
file_props['received-len'])
|
||||
else:
|
||||
ft.set_status(file_props['type'], file_props['sid'], 'stop')
|
||||
if 'stalled' in file_props and file_props['stalled'] or \
|
||||
'paused' in file_props and file_props['paused']:
|
||||
'paused' in file_props and file_props['paused']:
|
||||
return
|
||||
|
||||
if file_props['type'] == 'r': # we receive a file
|
||||
jid = unicode(file_props['sender'])
|
||||
# If we have a jingle session id, it is a jingle transfer
|
||||
# we compare hashes
|
||||
if 'session-sid' in file_props:
|
||||
# Compare hashes in a new thread
|
||||
self.hashThread = Thread(target=self.__compare_hashes,
|
||||
args=(account, file_props))
|
||||
self.hashThread.start()
|
||||
gajim.socks5queue.remove_receiver(file_props['sid'], True, True)
|
||||
else: # we send a file
|
||||
jid = unicode(file_props['receiver'])
|
||||
gajim.socks5queue.remove_sender(file_props['sid'], True, True)
|
||||
self.popup_ft_result(account, jid, file_props)
|
||||
|
||||
def popup_ft_result(self, account, jid, file_props):
|
||||
ft = self.instances['file_transfers']
|
||||
if helpers.allow_popup_window(account):
|
||||
if file_props['error'] == 0:
|
||||
if gajim.config.get('notify_on_file_complete'):
|
||||
|
@ -943,6 +988,8 @@ class Interface:
|
|||
elif file_props['error'] == -6:
|
||||
ft.show_stopped(jid, file_props,
|
||||
error_msg=_('Error opening file'))
|
||||
elif file_props['error'] == -10:
|
||||
ft.show_hash_error(jid, file_props)
|
||||
return
|
||||
|
||||
msg_type = ''
|
||||
|
@ -954,6 +1001,9 @@ class Interface:
|
|||
elif file_props['error'] in (-1, -6):
|
||||
msg_type = 'file-stopped'
|
||||
event_type = _('File Transfer Stopped')
|
||||
elif file_props['error'] == -10:
|
||||
msg_type = 'file-hash-error'
|
||||
event_type = _('File Transfer Failed')
|
||||
|
||||
if event_type == '':
|
||||
# FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
|
||||
|
@ -971,16 +1021,20 @@ class Interface:
|
|||
# get the name of the sender, as it is in the roster
|
||||
sender = unicode(file_props['sender']).split('/')[0]
|
||||
name = gajim.contacts.get_first_contact_from_jid(account,
|
||||
sender).get_shown_name()
|
||||
sender).get_shown_name()
|
||||
filename = os.path.basename(file_props['file-name'])
|
||||
if event_type == _('File Transfer Completed'):
|
||||
txt = _('You successfully received %(filename)s from '
|
||||
'%(name)s.') % {'filename': filename, 'name': name}
|
||||
img_name = 'gajim-ft_done'
|
||||
else: # ft stopped
|
||||
elif event_type == _('File Transfer Stopped'):
|
||||
txt = _('File transfer of %(filename)s from %(name)s '
|
||||
'stopped.') % {'filename': filename, 'name': name}
|
||||
img_name = 'gajim-ft_stopped'
|
||||
else: # ft hash error
|
||||
txt = _('File transfer of %(filename)s from %(name)s '
|
||||
'failed.') % {'filename': filename, 'name': name}
|
||||
img_name = 'gajim-ft_stopped'
|
||||
else:
|
||||
receiver = file_props['receiver']
|
||||
if hasattr(receiver, 'jid'):
|
||||
|
@ -988,24 +1042,28 @@ class Interface:
|
|||
receiver = receiver.split('/')[0]
|
||||
# get the name of the contact, as it is in the roster
|
||||
name = gajim.contacts.get_first_contact_from_jid(account,
|
||||
receiver).get_shown_name()
|
||||
receiver).get_shown_name()
|
||||
filename = os.path.basename(file_props['file-name'])
|
||||
if event_type == _('File Transfer Completed'):
|
||||
txt = _('You successfully sent %(filename)s to %(name)s.')\
|
||||
% {'filename': filename, 'name': name}
|
||||
% {'filename': filename, 'name': name}
|
||||
img_name = 'gajim-ft_done'
|
||||
else: # ft stopped
|
||||
elif event_type == _('File Transfer Stopped'):
|
||||
txt = _('File transfer of %(filename)s to %(name)s '
|
||||
'stopped.') % {'filename': filename, 'name': name}
|
||||
img_name = 'gajim-ft_stopped'
|
||||
else: # ft hash error
|
||||
txt = _('File transfer of %(filename)s to %(name)s '
|
||||
'failed.') % {'filename': filename, 'name': name}
|
||||
img_name = 'gajim-ft_stopped'
|
||||
path = gtkgui_helpers.get_icon_path(img_name, 48)
|
||||
else:
|
||||
txt = ''
|
||||
path = ''
|
||||
|
||||
if gajim.config.get('notify_on_file_complete') and \
|
||||
(gajim.config.get('autopopupaway') or \
|
||||
gajim.connections[account].connected in (2, 3)):
|
||||
(gajim.config.get('autopopupaway') or \
|
||||
gajim.connections[account].connected in (2, 3)):
|
||||
# we want to be notified and we are online/chat or we don't mind
|
||||
# bugged when away/na/busy
|
||||
notify.popup(event_type, jid, account, msg_type, path_to_image=path,
|
||||
|
@ -1103,6 +1161,23 @@ class Interface:
|
|||
'resource. Please type a new one'), resource=proposed_resource,
|
||||
ok_handler=on_ok)
|
||||
|
||||
def handle_event_jingleft_cancel(self, obj):
|
||||
ft = self.instances['file_transfers']
|
||||
file_props = None
|
||||
|
||||
# get the file_props of our session
|
||||
for sid in obj.conn.files_props:
|
||||
fp = obj.conn.files_props[sid]
|
||||
if fp['session-sid'] == obj.sid:
|
||||
file_props = fp
|
||||
break
|
||||
|
||||
ft.set_status(file_props['type'], file_props['sid'], 'stop')
|
||||
file_props['error'] = -4 # is it the right error code?
|
||||
|
||||
ft.show_stopped(obj.jid, file_props, 'Peer cancelled ' +
|
||||
'the transfer')
|
||||
|
||||
def handle_event_jingle_incoming(self, obj):
|
||||
# ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
|
||||
# data...))
|
||||
|
@ -1440,6 +1515,7 @@ class Interface:
|
|||
self.handle_event_jingle_disconnected],
|
||||
'jingle-error-received': [self.handle_event_jingle_error],
|
||||
'jingle-request-received': [self.handle_event_jingle_incoming],
|
||||
'jingleFT-cancelled-received': [self.handle_event_jingleft_cancel],
|
||||
'last-result-received': [self.handle_event_last_status_time],
|
||||
'message-error': [self.handle_event_msgerror],
|
||||
'message-not-sent': [self.handle_event_msgnotsent],
|
||||
|
@ -1498,7 +1574,7 @@ class Interface:
|
|||
no_queue = len(gajim.events.get_events(account, jid)) == 0
|
||||
# type_ can be gc-invitation file-send-error file-error
|
||||
# file-request-error file-request file-completed file-stopped
|
||||
# jingle-incoming
|
||||
# file-hash-error jingle-incoming
|
||||
# event_type can be in advancedNotificationWindow.events_list
|
||||
event_types = {'file-request': 'ft_request',
|
||||
'file-completed': 'ft_finished'}
|
||||
|
@ -1627,7 +1703,7 @@ class Interface:
|
|||
w = ctrl.parent_win
|
||||
elif type_ in ('normal', 'file-request', 'file-request-error',
|
||||
'file-send-error', 'file-error', 'file-stopped', 'file-completed',
|
||||
'jingle-incoming'):
|
||||
'file-hash-error', 'jingle-incoming'):
|
||||
# Get the first single message event
|
||||
event = gajim.events.get_first_event(account, fjid, type_)
|
||||
if not event:
|
||||
|
|
|
@ -25,7 +25,7 @@ import message_control
|
|||
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION
|
||||
from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION, NS_JINGLE_FILE_TRANSFER
|
||||
|
||||
def build_resources_submenu(contacts, account, action, room_jid=None,
|
||||
room_account=None, cap=None):
|
||||
|
@ -227,7 +227,7 @@ control=None, gc_contact=None):
|
|||
else:
|
||||
start_chat_menuitem.connect('activate',
|
||||
gajim.interface.on_open_chat_window, contact, account)
|
||||
if contact.supports(NS_FILE):
|
||||
if contact.supports(NS_FILE) or contact.supports(NS_JINGLE_FILE_TRANSFER):
|
||||
send_file_menuitem.set_sensitive(True)
|
||||
send_file_menuitem.connect('activate',
|
||||
roster.on_send_file_menuitem_activate, contact, account)
|
||||
|
|
|
@ -1926,6 +1926,9 @@ class RosterWindow:
|
|||
ft.show_stopped(jid, data, error_msg=msg_err)
|
||||
gajim.events.remove_events(account, jid, event)
|
||||
return True
|
||||
elif event.type_ == 'file-hash-error':
|
||||
ft.show_hash_error(jid, data)
|
||||
gajim.events.remove_events(account, jid, event)
|
||||
elif event.type_ == 'file-completed':
|
||||
ft.show_completed(jid, data)
|
||||
gajim.events.remove_events(account, jid, event)
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
'''
|
||||
Tests for dispatcher_nb.py
|
||||
'''
|
||||
import unittest
|
||||
|
||||
import lib
|
||||
lib.setup_env()
|
||||
|
||||
from mock import Mock
|
||||
|
||||
from common.protocol.bytestream import ConnectionIBBytestream, ConnectionSocks5Bytestream
|
||||
from common.xmpp import dispatcher_nb
|
||||
from common.xmpp import protocol
|
||||
from common.jingle import ConnectionJingle
|
||||
from common import gajim
|
||||
from common.socks5 import SocksQueue
|
||||
import common
|
||||
|
||||
|
||||
session_init = '''
|
||||
<iq xmlns="jabber:client" to="jingleft@thiessen.im/Gajim" type="set" id="43">
|
||||
<jingle xmlns="urn:xmpp:jingle:1" action="session-initiate" initiator="jtest@thiessen.im/Gajim" sid="38">
|
||||
<content name="fileWL1Y2JIPTM5RAD68" creator="initiator">
|
||||
<security xmlns="urn:xmpp:jingle:security:xtls:0">
|
||||
<method name="x509" />
|
||||
</security>
|
||||
<description xmlns="urn:xmpp:jingle:apps:file-transfer:1">
|
||||
<offer>
|
||||
<file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="to" size="2273">
|
||||
<desc />
|
||||
</file>
|
||||
</offer>
|
||||
</description>
|
||||
<transport xmlns="urn:xmpp:jingle:transports:s5b:1" sid="39">
|
||||
<candidate jid="jtest@thiessen.im/Gajim" cid="40" priority="8257536" host="192.168.2.100" type="direct" port="28011" />
|
||||
<candidate jid="proxy.thiessen.im" cid="41" priority="655360" host="192.168.2.100" type="proxy" port="5000" />
|
||||
<candidate jid="proxy.jabbim.cz" cid="42" priority="655360" host="192.168.2.100" type="proxy" port="7777" />
|
||||
</transport>
|
||||
</content>
|
||||
</jingle>
|
||||
</iq>
|
||||
'''
|
||||
|
||||
|
||||
transport_info = '''
|
||||
<iq from='jtest@thiessen.im/Gajim'
|
||||
id='hjdi8'
|
||||
to='jingleft@thiessen.im/Gajim'
|
||||
type='set'>
|
||||
<jingle xmlns='urn:xmpp:jingle:1'
|
||||
action='transport-info'
|
||||
initiator='jtest@thiessen.im/Gajim'
|
||||
sid='38'>
|
||||
<content creator='initiator' name='fileWL1Y2JIPTM5RAD68'>
|
||||
<transport xmlns='urn:xmpp:jingle:transports:s5b:1'
|
||||
sid='vj3hs98y'>
|
||||
<candidate-used cid='hr65dqyd'/>
|
||||
</transport>
|
||||
</content>
|
||||
</jingle>
|
||||
</iq>
|
||||
|
||||
'''
|
||||
|
||||
class Connection(Mock, ConnectionJingle, ConnectionSocks5Bytestream,
|
||||
ConnectionIBBytestream):
|
||||
|
||||
def __init__(self):
|
||||
Mock.__init__(self)
|
||||
ConnectionJingle.__init__(self)
|
||||
ConnectionSocks5Bytestream.__init__(self)
|
||||
ConnectionIBBytestream.__init__(self)
|
||||
self.connected = 2 # This tells gajim we are connected
|
||||
|
||||
|
||||
def send(self, stanza=None, when=None):
|
||||
# Called when gajim wants to send something
|
||||
print str(stanza)
|
||||
|
||||
class TestJingle(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.dispatcher = dispatcher_nb.XMPPDispatcher()
|
||||
gajim.nec = Mock()
|
||||
gajim.socks5queue = SocksQueue(Mock())
|
||||
# Setup mock client
|
||||
self.client = Connection()
|
||||
self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one?
|
||||
self.client._caller = Connection()
|
||||
self.client.defaultNamespace = protocol.NS_CLIENT
|
||||
self.client.Connection = Connection() # mock transport
|
||||
self.con = self.client.Connection
|
||||
self.con.server_resource = None
|
||||
self.con.connection = Connection()
|
||||
|
||||
'''
|
||||
Fake file_props when we recieve a file. Gajim creates a file_props
|
||||
out of a FileRequestRecieve event and from then on it changes in
|
||||
a lot of places. It is easier to just copy it in here.
|
||||
If the session_initiate stanza changes, this also must change.
|
||||
'''
|
||||
self.recieve_file = {'stream-methods':
|
||||
'http://jabber.org/protocol/bytestreams',
|
||||
'sender': u'jtest@thiessen.im/Gajim',
|
||||
'file-name': u'test_recieved_file',
|
||||
'request-id': u'43', 'sid': u'39',
|
||||
'session-sid': u'38', 'session-type': 'jingle',
|
||||
'transfered_size': [], 'receiver':
|
||||
u'jingleft@thiessen.im/Gajim', 'desc': '',
|
||||
u'size': u'2273', 'type': 'r',
|
||||
'streamhosts': [{'initiator':
|
||||
u'jtest@thiessen.im/Gajim',
|
||||
'target': u'jingleft@thiessen.im/Gajim',
|
||||
'cid': u'41', 'state': 0, 'host': u'192.168.2.100',
|
||||
'type': u'direct', 'port': u'28011'},
|
||||
{'initiator': u'jtest@thiessen.im/Gajim',
|
||||
'target': u'jingleft@thiessen.im/Gajim',
|
||||
'cid': u'42', 'state': 0, 'host': u'192.168.2.100',
|
||||
'type': u'proxy', 'port': u'5000'}],
|
||||
u'name': u'to'}
|
||||
|
||||
def tearDown(self):
|
||||
# Unplug if needed
|
||||
if hasattr(self.dispatcher, '_owner'):
|
||||
self.dispatcher.PlugOut()
|
||||
|
||||
def _simulate_connect(self):
|
||||
self.dispatcher.PlugIn(self.client) # client is owner
|
||||
# Simulate that we have established a connection
|
||||
self.dispatcher.StreamInit()
|
||||
self.dispatcher.ProcessNonBlocking("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'>")
|
||||
|
||||
def _simulate_jingle_session(self):
|
||||
|
||||
self.dispatcher.RegisterHandler('iq', self.con._JingleCB, 'set'
|
||||
, common.xmpp.NS_JINGLE)
|
||||
self.dispatcher.ProcessNonBlocking(session_init)
|
||||
session = self.con._sessions.values()[0] # The only session we have
|
||||
jft = session.contents.values()[0] # jingleFT object
|
||||
jft.file_props = self.recieve_file # We plug file_props manually
|
||||
# The user accepts to recieve the file
|
||||
# we have to manually simulate this behavior
|
||||
session.approve_session()
|
||||
self.con.send_file_approval(self.recieve_file)
|
||||
|
||||
self.dispatcher.ProcessNonBlocking(transport_info)
|
||||
|
||||
|
||||
def test_jingle_session(self):
|
||||
self._simulate_connect()
|
||||
self._simulate_jingle_session()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,177 @@
|
|||
'''
|
||||
Tests for dispatcher_nb.py
|
||||
'''
|
||||
import unittest
|
||||
|
||||
import lib
|
||||
lib.setup_env()
|
||||
|
||||
from mock import Mock
|
||||
import sys
|
||||
import socket
|
||||
|
||||
from common.socks5 import *
|
||||
from common import jingle_xtls
|
||||
|
||||
class fake_sock(Mock):
|
||||
def __init__(self, sockobj):
|
||||
Mock.__init__(self)
|
||||
|
||||
self.sockobj = sockobj
|
||||
|
||||
|
||||
def setup_stream(self):
|
||||
sha1 = self.sockobj._get_sha1_auth()
|
||||
|
||||
self.incoming = []
|
||||
self.incoming.append(self.sockobj._get_auth_response())
|
||||
self.incoming.append(
|
||||
self.sockobj._get_request_buff(sha1, 0x00)
|
||||
)
|
||||
self.outgoing = []
|
||||
self.outgoing.append(self.sockobj._get_auth_buff())
|
||||
self.outgoing.append(self.sockobj._get_request_buff(
|
||||
sha1
|
||||
))
|
||||
def switch_stream(self):
|
||||
# Roles are reversed, client will be expecting server stream
|
||||
# and server will be expecting client stream
|
||||
|
||||
temp = self.incoming
|
||||
self.incoming = self.outgoing
|
||||
self.outgoing = temp
|
||||
|
||||
def _recv(self, foo):
|
||||
return self.incoming.pop(0)
|
||||
|
||||
def _send(self, data):
|
||||
# This method is surrounded by a try block,
|
||||
# we can't use assert here
|
||||
|
||||
if data != self.outgoing[0]:
|
||||
print 'FAILED SENDING TEST'
|
||||
self.outgoing.pop(0)
|
||||
|
||||
class fake_idlequeue(Mock):
|
||||
|
||||
def __init__(self):
|
||||
Mock.__init__(self)
|
||||
|
||||
def plug_idle(self, obj, writable=True, readable=True):
|
||||
|
||||
if readable:
|
||||
obj.pollin()
|
||||
if writable:
|
||||
obj.pollout()
|
||||
|
||||
class TestSocks5(unittest.TestCase):
|
||||
'''
|
||||
Test class for Socks5
|
||||
'''
|
||||
def setUp(self):
|
||||
streamhost = { 'host': None,
|
||||
'port': 1,
|
||||
'initiator' : None,
|
||||
'target' : None}
|
||||
queue = Mock()
|
||||
queue.file_props = {}
|
||||
#self.sockobj = Socks5Receiver(fake_idlequeue(), streamhost, None)
|
||||
self.sockobj = Socks5Sender(fake_idlequeue(), None, 'server', Mock() ,
|
||||
None, None, True, file_props={})
|
||||
sock = fake_sock(self.sockobj)
|
||||
self.sockobj._sock = sock
|
||||
self.sockobj._recv = sock._recv
|
||||
self.sockobj._send = sock._send
|
||||
self.sockobj.state = 1
|
||||
self.sockobj.connected = True
|
||||
self.sockobj.pollend = self._pollend
|
||||
|
||||
# Something that the receiver needs
|
||||
#self.sockobj.file_props['type'] = 'r'
|
||||
|
||||
# Something that the sender needs
|
||||
self.sockobj.file_props = {}
|
||||
self.sockobj.file_props['type'] = 'r'
|
||||
self.sockobj.file_props['paused'] = ''
|
||||
self.sockobj.queue = Mock()
|
||||
self.sockobj.queue.process_result = self._pollend
|
||||
|
||||
def _pollend(self, foo = None, duu = None):
|
||||
# This is a disconnect function
|
||||
sys.exit("end of the road")
|
||||
|
||||
def _check_inout(self):
|
||||
# Check if there isn't anything else to receive or send
|
||||
sock = self.sockobj._sock
|
||||
assert(sock.incoming == [])
|
||||
assert(sock.outgoing == [])
|
||||
|
||||
def test_connection_server(self):
|
||||
return
|
||||
mocksock = self.sockobj._sock
|
||||
mocksock.setup_stream()
|
||||
#self.sockobj._sock.switch_stream()
|
||||
s = socket.socket(2, 1, 6)
|
||||
server = ('127.0.0.1', 28000)
|
||||
|
||||
s.connect(server)
|
||||
|
||||
s.send(mocksock.outgoing.pop(0))
|
||||
self.assertEquals(s.recv(64), mocksock.incoming.pop(0))
|
||||
|
||||
s.send(mocksock.outgoing.pop(0))
|
||||
self.assertEquals(s.recv(64), mocksock.incoming.pop(0))
|
||||
|
||||
def test_connection_client(self):
|
||||
|
||||
|
||||
mocksock = self.sockobj._sock
|
||||
mocksock.setup_stream()
|
||||
mocksock.switch_stream()
|
||||
s = socket.socket(10, 1, 6)
|
||||
|
||||
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
netadd = ('::', 28000, 0, 0)
|
||||
s.bind(netadd)
|
||||
s.listen(socket.SOMAXCONN)
|
||||
(s, address) = s.accept()
|
||||
|
||||
|
||||
self.assertEquals(s.recv(64), mocksock.incoming.pop(0))
|
||||
s.send(mocksock.outgoing.pop(0))
|
||||
|
||||
buff = s.recv(64)
|
||||
inco = mocksock.incoming.pop(0)
|
||||
#self.assertEquals(s.recv(64), mocksock.incoming.pop(0))
|
||||
s.send(mocksock.outgoing.pop(0))
|
||||
|
||||
def test_client_negoc(self):
|
||||
return
|
||||
self.sockobj._sock.setup_stream()
|
||||
try:
|
||||
self.sockobj.pollout()
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
self._check_inout()
|
||||
|
||||
def test_server_negoc(self):
|
||||
return
|
||||
self.sockobj._sock.setup_stream()
|
||||
self.sockobj._sock.switch_stream()
|
||||
try:
|
||||
self.sockobj.idlequeue.plug_idle(self.sockobj, False, True)
|
||||
except SystemExit:
|
||||
pass
|
||||
self._check_inout()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
unittest.main()
|
Loading…
Reference in New Issue