plugin XEP-0234: hash checksum proper handling
This commit is contained in:
parent
ed8d3db308
commit
51aecd012a
1 changed files with 116 additions and 9 deletions
|
|
@ -31,6 +31,8 @@ from twisted.words.protocols.jabber import jid
|
|||
from twisted.python import failure
|
||||
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
|
||||
from twisted.internet import defer
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet import error as internet_error
|
||||
|
||||
|
||||
NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:4'
|
||||
|
|
@ -49,7 +51,7 @@ PLUGIN_INFO = {
|
|||
|
||||
class XEP_0234(object):
|
||||
# TODO: assure everything is closed when file is sent or session terminate is received
|
||||
# TODO: call self._f.unregister when unloading order will be managing (i.e. when depenencies will be unloaded at the end)
|
||||
# TODO: call self._f.unregister when unloading order will be managing (i.e. when dependencies will be unloaded at the end)
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("plugin Jingle File Transfer initialization"))
|
||||
|
|
@ -138,6 +140,16 @@ class XEP_0234(object):
|
|||
except StopIteration:
|
||||
file_data[name] = ''
|
||||
|
||||
try:
|
||||
hash_algo, file_data['hash_given'] = self._hash.parseHashElt(file_elt)
|
||||
except exceptions.NotFound:
|
||||
raise failure.Failure(exceptions.DataError)
|
||||
|
||||
if hash_algo is not None:
|
||||
file_data['hash_algo'] = hash_algo
|
||||
file_data['hash_hasher'] = hasher = self._hash.getHasher(hash_algo)
|
||||
file_data['data_cb'] = lambda data: hasher.update(data)
|
||||
|
||||
try:
|
||||
file_data['size'] = int(file_data['size'])
|
||||
except ValueError:
|
||||
|
|
@ -148,8 +160,6 @@ class XEP_0234(object):
|
|||
log.warning(u"File name contain path characters, we replace them: {}".format(name))
|
||||
file_data['name'] = name.replace('/', '_').replace('\\', '_')
|
||||
|
||||
# TODO: parse hash using plugin XEP-0300
|
||||
|
||||
content_data['application_data']['file_data'] = file_data
|
||||
|
||||
# now we actualy request permission to user
|
||||
|
|
@ -164,7 +174,6 @@ class XEP_0234(object):
|
|||
d.addCallback(gotConfirmation)
|
||||
return d
|
||||
|
||||
|
||||
def jingleHandler(self, action, session, content_name, desc_elt, profile):
|
||||
content_data = session['contents'][content_name]
|
||||
application_data = content_data['application_data']
|
||||
|
|
@ -180,12 +189,16 @@ class XEP_0234(object):
|
|||
file_elt.addElement('range')
|
||||
elif action == self._j.A_SESSION_ACCEPT:
|
||||
assert not 'file_obj' in content_data
|
||||
file_data = application_data['file_data']
|
||||
file_path = application_data['file_path']
|
||||
size = application_data['file_data']['size']
|
||||
size = file_data['size']
|
||||
# XXX: hash security is not critical here, so we just take the higher mandatory one
|
||||
hasher = file_data['hash_hasher'] = self._hash.getHasher('sha-256')
|
||||
content_data['file_obj'] = self._f.File(self.host,
|
||||
file_path,
|
||||
uid=self._getProgressId(session, content_name),
|
||||
size=size,
|
||||
data_cb=lambda data: hasher.update(data),
|
||||
profile=profile
|
||||
)
|
||||
finished_d = content_data['finished_d'] = defer.Deferred()
|
||||
|
|
@ -195,13 +208,107 @@ class XEP_0234(object):
|
|||
log.warning(u"FIXME: unmanaged action {}".format(action))
|
||||
return desc_elt
|
||||
|
||||
def _finishedCb(self, dummy, session, content_name, content_data, profile):
|
||||
log.info(u"File transfer completed successfuly")
|
||||
if content_data['senders'] != session['role']:
|
||||
# we terminate the session only if we are the received,
|
||||
# as recommanded in XEP-0234 §2 (after example 6)
|
||||
self._j.contentTerminate(session, content_name, profile=profile)
|
||||
def jingleSessionInfo(self, action, session, content_name, jingle_elt, profile):
|
||||
"""Called on session-info action
|
||||
|
||||
manage checksum, and ignore <received/> element
|
||||
"""
|
||||
# TODO: manage <received/> element
|
||||
content_data = session['contents'][content_name]
|
||||
elts = [elt for elt in jingle_elt.elements() if elt.uri == NS_JINGLE_FT]
|
||||
if not elts:
|
||||
return
|
||||
for elt in elts:
|
||||
if elt.name == 'received':
|
||||
pass
|
||||
elif elt.name == 'checksum':
|
||||
# we have received the file hash, we need to parse it
|
||||
if content_data['senders'] == session['role']:
|
||||
log.warning(u"unexpected checksum received while we are the file sender")
|
||||
raise exceptions.DataError
|
||||
info_content_name = elt['name']
|
||||
if info_content_name != content_name:
|
||||
# it was for an other content...
|
||||
return
|
||||
file_data = content_data['application_data']['file_data']
|
||||
try:
|
||||
file_elt = elt.elements((NS_JINGLE_FT, 'file')).next()
|
||||
except StopIteration:
|
||||
raise exceptions.DataError
|
||||
algo, file_data['hash_given'] = self._hash.parseHashElt(file_elt)
|
||||
if algo != file_data.get('hash_algo'):
|
||||
log.warning(u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo})"
|
||||
.format(peer_algo=algo, our_algo=file_data.get('hash_algo')))
|
||||
else:
|
||||
self._receiverTryTerminate(session, content_name, content_data, profile=profile)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def _sendCheckSum(self, session, content_name, content_data, profile):
|
||||
"""Send the session-info with the hash checksum"""
|
||||
file_data = content_data['application_data']['file_data']
|
||||
hasher = file_data['hash_hasher']
|
||||
hash_ = hasher.hexdigest()
|
||||
log.debug(u"Calculated hash: {}".format(hash_))
|
||||
iq_elt, jingle_elt = self._j.buildSessionInfo(session, profile)
|
||||
checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, 'checksum'))
|
||||
checksum_elt['creator'] = content_data['creator']
|
||||
checksum_elt['name'] = content_name
|
||||
file_elt = checksum_elt.addElement('file')
|
||||
file_elt.addChild(self._hash.buildHashElt(hash_))
|
||||
iq_elt.send()
|
||||
|
||||
def _receiverTryTerminate(self, session, content_name, content_data, last_try=False, profile=C.PROF_KEY_NONE):
|
||||
"""Try to terminate the session
|
||||
|
||||
This method must only be used by the receiver.
|
||||
It check if transfer is finished, and hash available,
|
||||
if everything is OK, it check hash and terminate the session
|
||||
@param last_try(bool): if True this mean than session must be terminated even given hash is not available
|
||||
@return (bool): True if session was terminated
|
||||
"""
|
||||
if not content_data.get('transfer_finished', False):
|
||||
return False
|
||||
file_data = content_data['application_data']['file_data']
|
||||
hash_given = file_data.get('hash_given')
|
||||
if hash_given is None:
|
||||
if last_try:
|
||||
log.warning(u"sender didn't sent hash checksum, we can't check the file")
|
||||
self._j.delayedContentTerminate(session, content_name, profile=profile)
|
||||
content_data['file_obj'].close()
|
||||
return True
|
||||
return False
|
||||
hasher = file_data['hash_hasher']
|
||||
hash_ = hasher.hexdigest()
|
||||
|
||||
if hash_ == hash_given:
|
||||
log.info(u"Hash checked, file was successfully transfered: {}".format(hash_))
|
||||
else:
|
||||
log.warning(u"Hash mismatch, the file was not transfered correctly")
|
||||
|
||||
self._j.delayedContentTerminate(session, content_name, profile=profile)
|
||||
content_data['file_obj'].close()
|
||||
# we may have the last_try timer still active, so we try to cancel it
|
||||
try:
|
||||
content_data['last_try_timer'].cancel()
|
||||
except (KeyError, internet_error.AlreadyCalled):
|
||||
pass
|
||||
return True
|
||||
|
||||
def _finishedCb(self, dummy, session, content_name, content_data, profile):
|
||||
log.info(u"File transfer completed")
|
||||
if content_data['senders'] != session['role']:
|
||||
# we terminate the session only if we are the receiver,
|
||||
# as recommanded in XEP-0234 §2 (after example 6)
|
||||
content_data['transfer_finished'] = True
|
||||
if not self._receiverTryTerminate(session, content_name, content_data, profile=profile):
|
||||
# we have not received the hash yet, we wait 5 more seconds
|
||||
content_data['last_try_timer'] = reactor.callLater(
|
||||
5, self._receiverTryTerminate, session, content_name, content_data, last_try=True, profile=profile)
|
||||
else:
|
||||
# we are the sender, we send the checksum
|
||||
self._sendCheckSum(session, content_name, content_data, profile)
|
||||
content_data['file_obj'].close()
|
||||
|
||||
def _finishedEb(self, failure, session, content_name, content_data, profile):
|
||||
log.warning(u"Error while streaming through s5b: {}".format(failure))
|
||||
|
|
|
|||
Loading…
Reference in a new issue