support for xep-300

This commit is contained in:
Jefry Lagrange 2012-01-15 19:37:00 -05:00
parent 76bc866a98
commit 376777091e
5 changed files with 161 additions and 14 deletions

View File

@ -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 = {}

View File

@ -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:

View File

@ -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-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]
@ -99,12 +101,36 @@ class JingleFileTransfer(JingleContent):
self.session = session
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")
con = self.session.connection
@ -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:
@ -392,7 +419,7 @@ class JingleFileTransfer(JingleContent):
def start_transfer(self):
self.state = STATE_TRANSFERING
# It tells wether we start the transfer as client or server

View File

@ -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
@ -126,6 +126,7 @@ class JingleSession(object):
'iq-result': [self.__broadcast],
'iq-error': [self.__on_error],
}
def collect_iq_id(self, iq_id):
if iq_id is not None:
@ -416,9 +417,20 @@ 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:
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'):
@ -704,6 +716,13 @@ 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

View File

@ -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,7 +161,12 @@ 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.
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()})
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)