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