Mercurial > libervia-backend
diff src/plugins/plugin_xep_0300.py @ 2502:7ad5f2c4e34a
XEP-0065,XEP-0096,XEP-0166,XEP-0235,XEP-0300: file transfer improvments:
huge patch sorry :)
many things are improved by this patch, notably:
- updated to last protocol changes (urn:xmpp:jingle:apps:file-transfer:5 and urn:xmpp:hashes:2)
- XEP-0234: file request implementation
- XEP-0234: methods to parse and generate <file> element (can be used by other plugins easily)
- XEP-0234: range data is now in a namedtuple
- path and namespace can be specified when sending/requesting a file (not standard, but needed for file sharing)
- better error/termination handling
- trigger points to handle file requests by other plugins
- preparation to use file plugins with components
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 28 Feb 2018 18:28:39 +0100 |
parents | 0046283a285d |
children |
line wrap: on
line diff
--- a/src/plugins/plugin_xep_0300.py Wed Feb 28 18:28:39 2018 +0100 +++ b/src/plugins/plugin_xep_0300.py Wed Feb 28 18:28:39 2018 +0100 @@ -30,25 +30,28 @@ from wokkel import disco, iwokkel from collections import OrderedDict import hashlib +import base64 PLUGIN_INFO = { C.PI_NAME: "Cryptographic Hash Functions", C.PI_IMPORT_NAME: "XEP-0300", C.PI_TYPE: "XEP", + C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0300"], C.PI_MAIN: "XEP_0300", C.PI_HANDLER: "yes", C.PI_DESCRIPTION: _("""Management of cryptographic hashes""") } -NS_HASHES = "urn:xmpp:hashes:1" +NS_HASHES = "urn:xmpp:hashes:2" NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}" BUFFER_SIZE = 2**12 ALGO_DEFAULT = 'sha-256' class XEP_0300(object): + # TODO: add blake after moving to Python 3 ALGOS = OrderedDict(( (u'md5', hashlib.md5), (u'sha-1', hashlib.sha1), @@ -58,22 +61,24 @@ def __init__(self, host): log.info(_("plugin Hashes initialization")) + host.registerNamespace('hashes', NS_HASHES) def getHandler(self, client): return XEP_0300_handler() - def getHasher(self, algo): + def getHasher(self, algo=ALGO_DEFAULT): """Return hasher instance - /!\\ blocking method, considere using calculateHashElt - if you want to hash a big file - @param algo(unicode): one of the XEP_300.ALGOS keys - @return (hash object): same object s in hashlib. - update method need to be called for each chunh - diget or hexdigest can be used at the end + @param algo(unicode): one of the XEP_300.ALGOS keys + @return (hash object): same object s in hashlib. + update method need to be called for each chunh + diget or hexdigest can be used at the end """ return self.ALGOS[algo]() + def getDefaultAlgo(self): + return ALGO_DEFAULT + @defer.inlineCallbacks def getBestPeerAlgo(self, to_jid, profile): """Return the best available hashing algorith of other peer @@ -92,9 +97,10 @@ algo=algo)) defer.returnValue(algo) - def calculateHashBlocking(self, file_obj, hasher): + def _calculateHashBlocking(self, file_obj, hasher): """Calculate hash in a blocking way + /!\\ blocking method, please use calculateHash instead @param file_obj(file): a file-like object @param hasher(callable): the method to call to initialise hash object @return (str): the hex digest of the hash @@ -107,6 +113,9 @@ hash_.update(buf) return hash_.hexdigest() + def calculateHash(self, file_obj, hasher): + return threads.deferToThread(self._calculateHashBlocking, file_obj, hasher) + def calculateHashElt(self, file_obj=None, algo=ALGO_DEFAULT): """Compute hash and build hash element @@ -117,21 +126,45 @@ def hashCalculated(hash_): return self.buildHashElt(hash_, algo) hasher = self.ALGOS[algo] - hash_d = threads.deferToThread(self.calculateHashBlocking, file_obj, hasher) + hash_d = self.calculateHash(file_obj, hasher) hash_d.addCallback(hashCalculated) return hash_d - def buildHashElt(self, hash_=None, algo=ALGO_DEFAULT): + def buildHashUsedElt(self, algo=ALGO_DEFAULT): + hash_used_elt = domish.Element((NS_HASHES, 'hash-used')) + hash_used_elt['algo'] = algo + return hash_used_elt + + def parseHashUsedElt(self, parent): + """Find and parse a hash-used element + + @param (domish.Element): parent of <hash/> element + @return (unicode): hash algorithm used + @raise exceptions.NotFound: the element is not present + @raise exceptions.DataError: the element is invalid + """ + try: + hash_used_elt = next(parent.elements(NS_HASHES, 'hash-used')) + except StopIteration: + raise exceptions.NotFound + algo = hash_used_elt[u'algo'] + if not algo: + raise exceptions.DataError + return algo + + def buildHashElt(self, hash_, algo=ALGO_DEFAULT): """Compute hash and build hash element - @param hash_(None, str): hash to use, or None for an empty element + @param hash_(str): hash to use @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS @return (domish.Element): computed hash """ + assert hash_ + assert algo hash_elt = domish.Element((NS_HASHES, 'hash')) if hash_ is not None: - hash_elt.addContent(hash_) - hash_elt['algo']=algo + hash_elt.addContent(base64.b64encode(hash_)) + hash_elt['algo'] = algo return hash_elt def parseHashElt(self, parent): @@ -142,6 +175,7 @@ @return (tuple[unicode, str]): (algo, hash) tuple both values can be None if <hash/> is empty @raise exceptions.NotFound: the element is not present + @raise exceptions.DataError: the element is invalid """ algos = XEP_0300.ALGOS.keys() hash_elt = None @@ -158,10 +192,12 @@ if best_algo is None or algos.index(best_algo) < idx: best_algo = algo - best_value = str(hash_elt) or None + best_value = base64.b64decode(unicode(hash_elt)) if not hash_elt: raise exceptions.NotFound + if not best_algo or not best_value: + raise exceptions.DataError return best_algo, best_value