support for xep-300
This commit is contained in:
parent
76bc866a98
commit
376777091e
5 changed files with 161 additions and 14 deletions
|
@ -208,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,
|
'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',
|
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,
|
'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
|
# Optional features gajim supports per account
|
||||||
gajim_optional_features = {}
|
gajim_optional_features = {}
|
||||||
|
|
|
@ -151,10 +151,24 @@ class ConnectionJingle(object):
|
||||||
file_props['sid'] = jingle.sid
|
file_props['sid'] = jingle.sid
|
||||||
c = JingleFileTransfer(jingle, file_props=file_props,
|
c = JingleFileTransfer(jingle, file_props=file_props,
|
||||||
use_security=use_security)
|
use_security=use_security)
|
||||||
|
c.hash_algo = self.__hash_support(contact)
|
||||||
jingle.add_content('file' + helpers.get_random_string_16(), c)
|
jingle.add_content('file' + helpers.get_random_string_16(), c)
|
||||||
jingle.start_session()
|
jingle.start_session()
|
||||||
return c.transport.sid
|
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):
|
def iter_jingle_sessions(self, jid, sid=None, media=None):
|
||||||
if sid:
|
if sid:
|
||||||
|
|
|
@ -26,7 +26,7 @@ from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5, Jingl
|
||||||
from common import helpers
|
from common import helpers
|
||||||
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
|
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
|
||||||
from common.connection_handlers_events import FileRequestReceivedEvent
|
from common.connection_handlers_events import FileRequestReceivedEvent
|
||||||
|
import threading
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger('gajim.c.jingle_ft')
|
log = logging.getLogger('gajim.c.jingle_ft')
|
||||||
|
|
||||||
|
@ -56,9 +56,11 @@ class JingleFileTransfer(JingleContent):
|
||||||
|
|
||||||
# events we might be interested in
|
# events we might be interested in
|
||||||
self.callbacks['session-initiate'] += [self.__on_session_initiate]
|
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['content-add'] += [self.__on_session_initiate]
|
||||||
self.callbacks['session-accept'] += [self.__on_session_accept]
|
self.callbacks['session-accept'] += [self.__on_session_accept]
|
||||||
self.callbacks['session-terminate'] += [self.__on_session_terminate]
|
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-accept'] += [self.__on_transport_accept]
|
||||||
self.callbacks['transport-replace'] += [self.__on_transport_replace]
|
self.callbacks['transport-replace'] += [self.__on_transport_replace]
|
||||||
self.callbacks['session-accept-sent'] += [self.__transport_setup]
|
self.callbacks['session-accept-sent'] += [self.__transport_setup]
|
||||||
|
@ -99,12 +101,36 @@ class JingleFileTransfer(JingleContent):
|
||||||
self.session = session
|
self.session = session
|
||||||
self.media = 'file'
|
self.media = 'file'
|
||||||
self.nominated_cand = {}
|
self.nominated_cand = {}
|
||||||
|
|
||||||
|
# Hash algorithm that we are using to calculate the integrity of the
|
||||||
|
# file. Could be 'md5', 'sha-1', etc...
|
||||||
|
self.hash_algo = None
|
||||||
|
|
||||||
def __on_session_initiate(self, stanza, content, error, action):
|
def __on_session_initiate(self, stanza, content, error, action):
|
||||||
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
|
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
|
||||||
conn=self.session.connection, stanza=stanza, jingle_content=content,
|
conn=self.session.connection, stanza=stanza, jingle_content=content,
|
||||||
FT_content=self))
|
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.hash_algo == None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
file = open(self.file_props['file-name'], 'r')
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
h = xmpp.Hashes()
|
||||||
|
h.calculateHash(self.hash_algo, file)
|
||||||
|
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):
|
def __on_session_accept(self, stanza, content, error, action):
|
||||||
log.info("__on_session_accept")
|
log.info("__on_session_accept")
|
||||||
con = self.session.connection
|
con = self.session.connection
|
||||||
|
@ -152,6 +178,9 @@ class JingleFileTransfer(JingleContent):
|
||||||
def __on_session_terminate(self, stanza, content, error, action):
|
def __on_session_terminate(self, stanza, content, error, action):
|
||||||
log.info("__on_session_terminate")
|
log.info("__on_session_terminate")
|
||||||
|
|
||||||
|
def __on_session_info(self, stanza, content, error, action):
|
||||||
|
pass
|
||||||
|
|
||||||
def __on_transport_accept(self, stanza, content, error, action):
|
def __on_transport_accept(self, stanza, content, error, action):
|
||||||
log.info("__on_transport_accept")
|
log.info("__on_transport_accept")
|
||||||
|
|
||||||
|
@ -164,8 +193,6 @@ class JingleFileTransfer(JingleContent):
|
||||||
def __on_transport_info(self, stanza, content, error, action):
|
def __on_transport_info(self, stanza, content, error, action):
|
||||||
log.info("__on_transport_info")
|
log.info("__on_transport_info")
|
||||||
|
|
||||||
#if not self.weinitiate: # proxy activated from initiator
|
|
||||||
# return
|
|
||||||
if content.getTag('transport').getTag('candidate-error'):
|
if content.getTag('transport').getTag('candidate-error'):
|
||||||
self.nominated_cand['peer-cand'] = False
|
self.nominated_cand['peer-cand'] = False
|
||||||
if self.state == STATE_CAND_SENT_PENDING_REPLY:
|
if self.state == STATE_CAND_SENT_PENDING_REPLY:
|
||||||
|
@ -392,7 +419,7 @@ class JingleFileTransfer(JingleContent):
|
||||||
|
|
||||||
|
|
||||||
def start_transfer(self):
|
def start_transfer(self):
|
||||||
|
|
||||||
self.state = STATE_TRANSFERING
|
self.state = STATE_TRANSFERING
|
||||||
|
|
||||||
# It tells wether we start the transfer as client or server
|
# It tells wether we start the transfer as client or server
|
||||||
|
|
|
@ -98,7 +98,7 @@ class JingleSession(object):
|
||||||
|
|
||||||
|
|
||||||
self.accepted = True # is this session accepted by user
|
self.accepted = True # is this session accepted by user
|
||||||
|
self.file_hash = None
|
||||||
# callbacks to call on proper contents
|
# callbacks to call on proper contents
|
||||||
# use .prepend() to add new callbacks, especially when you're going
|
# use .prepend() to add new callbacks, especially when you're going
|
||||||
# to send error instead of ack
|
# to send error instead of ack
|
||||||
|
@ -126,6 +126,7 @@ class JingleSession(object):
|
||||||
'iq-result': [self.__broadcast],
|
'iq-result': [self.__broadcast],
|
||||||
'iq-error': [self.__on_error],
|
'iq-error': [self.__on_error],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def collect_iq_id(self, iq_id):
|
def collect_iq_id(self, iq_id):
|
||||||
if iq_id is not None:
|
if iq_id is not None:
|
||||||
|
@ -416,9 +417,20 @@ class JingleSession(object):
|
||||||
def __on_session_info(self, stanza, jingle, error, action):
|
def __on_session_info(self, stanza, jingle, error, action):
|
||||||
# TODO: ringing, active, (un)hold, (un)mute
|
# TODO: ringing, active, (un)hold, (un)mute
|
||||||
payload = jingle.getPayload()
|
payload = jingle.getPayload()
|
||||||
if payload:
|
for p in payload:
|
||||||
self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify')
|
if p.getName() == 'checksum':
|
||||||
raise xmpp.NodeProcessed
|
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:
|
||||||
|
data = hash.getData()
|
||||||
|
self.file_hash = data
|
||||||
|
print 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):
|
def __on_content_remove(self, stanza, jingle, error, action):
|
||||||
for content in jingle.iterTags('content'):
|
for content in jingle.iterTags('content'):
|
||||||
|
@ -704,6 +716,13 @@ class JingleSession(object):
|
||||||
if payload:
|
if payload:
|
||||||
jingle.addChild(node=payload)
|
jingle.addChild(node=payload)
|
||||||
self.connection.connection.send(stanza)
|
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):
|
def _session_terminate(self, reason=None):
|
||||||
assert self.state != JingleStates.ended
|
assert self.state != JingleStates.ended
|
||||||
|
|
|
@ -23,6 +23,7 @@ sub- stanzas) handling routines
|
||||||
from simplexml import Node, NodeBuilder
|
from simplexml import Node, NodeBuilder
|
||||||
import time
|
import time
|
||||||
import string
|
import string
|
||||||
|
import hashlib
|
||||||
|
|
||||||
def ascii_upper(s):
|
def ascii_upper(s):
|
||||||
trans_table = string.maketrans(string.ascii_lowercase,
|
trans_table = string.maketrans(string.ascii_lowercase,
|
||||||
|
@ -89,7 +90,7 @@ NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166
|
||||||
NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167
|
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_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167
|
||||||
NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167
|
NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167
|
||||||
NS_JINGLE_FILE_TRANSFER='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234
|
NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:1' # XEP-0234
|
||||||
NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle
|
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_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_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176
|
||||||
|
@ -160,7 +161,12 @@ NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2'
|
||||||
NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2'
|
NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2'
|
||||||
NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2'
|
NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2'
|
||||||
NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198
|
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 = '''
|
xmpp_stream_error_conditions = '''
|
||||||
bad-format -- -- -- The entity has sent XML that cannot be processed.
|
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.
|
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.
|
||||||
|
@ -1030,7 +1036,85 @@ class Iq(Protocol):
|
||||||
attrs={'id': self.getID()})
|
attrs={'id': self.getID()})
|
||||||
iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
|
iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
|
||||||
return iq
|
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 == None:
|
||||||
|
# Raise exception
|
||||||
|
raise Exception('Hash algorithm not supported')
|
||||||
|
else:
|
||||||
|
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 == None:
|
||||||
|
# Raise exception
|
||||||
|
raise Exception('Hash algorithm not supported')
|
||||||
|
else:
|
||||||
|
for line in file_string:
|
||||||
|
hl.update(line)
|
||||||
|
hash = hl.hexdigest()
|
||||||
|
|
||||||
|
self.addHash(hash, algo)
|
||||||
|
|
||||||
|
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):
|
class Acks(Node):
|
||||||
"""
|
"""
|
||||||
Acknowledgement elements for Stream Management
|
Acknowledgement elements for Stream Management
|
||||||
|
@ -1413,3 +1497,4 @@ class DataForm(Node):
|
||||||
Simple dictionary interface for setting datafields values by their names
|
Simple dictionary interface for setting datafields values by their names
|
||||||
"""
|
"""
|
||||||
return self.setField(name).setValue(val)
|
return self.setField(name).setValue(val)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue