support for xep-300
This commit is contained in:
parent
76bc866a98
commit
376777091e
|
@ -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,
|
||||
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 = {}
|
||||
|
|
|
@ -151,10 +151,24 @@ class ConnectionJingle(object):
|
|||
file_props['sid'] = jingle.sid
|
||||
c = JingleFileTransfer(jingle, file_props=file_props,
|
||||
use_security=use_security)
|
||||
c.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:
|
||||
|
|
|
@ -26,7 +26,7 @@ from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5, Jingl
|
|||
from common import helpers
|
||||
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
|
||||
from common.connection_handlers_events import FileRequestReceivedEvent
|
||||
|
||||
import threading
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.jingle_ft')
|
||||
|
||||
|
@ -56,9 +56,11 @@ class JingleFileTransfer(JingleContent):
|
|||
|
||||
# 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]
|
||||
|
@ -100,10 +102,34 @@ class JingleFileTransfer(JingleContent):
|
|||
self.media = 'file'
|
||||
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):
|
||||
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.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):
|
||||
log.info("__on_session_accept")
|
||||
|
@ -152,6 +178,9 @@ class JingleFileTransfer(JingleContent):
|
|||
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")
|
||||
|
||||
|
@ -164,8 +193,6 @@ class JingleFileTransfer(JingleContent):
|
|||
def __on_transport_info(self, stanza, content, error, action):
|
||||
log.info("__on_transport_info")
|
||||
|
||||
#if not self.weinitiate: # proxy activated from initiator
|
||||
# return
|
||||
if content.getTag('transport').getTag('candidate-error'):
|
||||
self.nominated_cand['peer-cand'] = False
|
||||
if self.state == STATE_CAND_SENT_PENDING_REPLY:
|
||||
|
|
|
@ -98,7 +98,7 @@ class JingleSession(object):
|
|||
|
||||
|
||||
self.accepted = True # is this session accepted by user
|
||||
|
||||
self.file_hash = None
|
||||
# callbacks to call on proper contents
|
||||
# use .prepend() to add new callbacks, especially when you're going
|
||||
# to send error instead of ack
|
||||
|
@ -127,6 +127,7 @@ class JingleSession(object):
|
|||
'iq-error': [self.__on_error],
|
||||
}
|
||||
|
||||
|
||||
def collect_iq_id(self, iq_id):
|
||||
if iq_id is not None:
|
||||
self.iq_ids.append(iq_id)
|
||||
|
@ -416,10 +417,21 @@ class JingleSession(object):
|
|||
def __on_session_info(self, stanza, jingle, error, action):
|
||||
# TODO: ringing, active, (un)hold, (un)mute
|
||||
payload = jingle.getPayload()
|
||||
if payload:
|
||||
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:
|
||||
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):
|
||||
for content in jingle.iterTags('content'):
|
||||
creator = content['creator']
|
||||
|
@ -705,6 +717,13 @@ class JingleSession(object):
|
|||
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)
|
||||
|
|
|
@ -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,
|
||||
|
@ -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_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: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_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177
|
||||
NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176
|
||||
|
@ -160,6 +161,11 @@ NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2'
|
|||
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.
|
||||
|
@ -1031,6 +1037,84 @@ class Iq(Protocol):
|
|||
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 == 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):
|
||||
"""
|
||||
Acknowledgement elements for Stream Management
|
||||
|
@ -1413,3 +1497,4 @@ class DataForm(Node):
|
|||
Simple dictionary interface for setting datafields values by their names
|
||||
"""
|
||||
return self.setField(name).setValue(val)
|
||||
|
||||
|
|
Loading…
Reference in New Issue