comparison 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
comparison
equal deleted inserted replaced
2501:3b67fe672206 2502:7ad5f2c4e34a
28 from twisted.internet import defer 28 from twisted.internet import defer
29 from zope.interface import implements 29 from zope.interface import implements
30 from wokkel import disco, iwokkel 30 from wokkel import disco, iwokkel
31 from collections import OrderedDict 31 from collections import OrderedDict
32 import hashlib 32 import hashlib
33 import base64
33 34
34 35
35 PLUGIN_INFO = { 36 PLUGIN_INFO = {
36 C.PI_NAME: "Cryptographic Hash Functions", 37 C.PI_NAME: "Cryptographic Hash Functions",
37 C.PI_IMPORT_NAME: "XEP-0300", 38 C.PI_IMPORT_NAME: "XEP-0300",
38 C.PI_TYPE: "XEP", 39 C.PI_TYPE: "XEP",
40 C.PI_MODES: C.PLUG_MODE_BOTH,
39 C.PI_PROTOCOLS: ["XEP-0300"], 41 C.PI_PROTOCOLS: ["XEP-0300"],
40 C.PI_MAIN: "XEP_0300", 42 C.PI_MAIN: "XEP_0300",
41 C.PI_HANDLER: "yes", 43 C.PI_HANDLER: "yes",
42 C.PI_DESCRIPTION: _("""Management of cryptographic hashes""") 44 C.PI_DESCRIPTION: _("""Management of cryptographic hashes""")
43 } 45 }
44 46
45 NS_HASHES = "urn:xmpp:hashes:1" 47 NS_HASHES = "urn:xmpp:hashes:2"
46 NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}" 48 NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}"
47 BUFFER_SIZE = 2**12 49 BUFFER_SIZE = 2**12
48 ALGO_DEFAULT = 'sha-256' 50 ALGO_DEFAULT = 'sha-256'
49 51
50 52
51 class XEP_0300(object): 53 class XEP_0300(object):
54 # TODO: add blake after moving to Python 3
52 ALGOS = OrderedDict(( 55 ALGOS = OrderedDict((
53 (u'md5', hashlib.md5), 56 (u'md5', hashlib.md5),
54 (u'sha-1', hashlib.sha1), 57 (u'sha-1', hashlib.sha1),
55 (u'sha-256', hashlib.sha256), 58 (u'sha-256', hashlib.sha256),
56 (u'sha-512', hashlib.sha512), 59 (u'sha-512', hashlib.sha512),
57 )) 60 ))
58 61
59 def __init__(self, host): 62 def __init__(self, host):
60 log.info(_("plugin Hashes initialization")) 63 log.info(_("plugin Hashes initialization"))
64 host.registerNamespace('hashes', NS_HASHES)
61 65
62 def getHandler(self, client): 66 def getHandler(self, client):
63 return XEP_0300_handler() 67 return XEP_0300_handler()
64 68
65 def getHasher(self, algo): 69 def getHasher(self, algo=ALGO_DEFAULT):
66 """Return hasher instance 70 """Return hasher instance
67 71
68 /!\\ blocking method, considere using calculateHashElt 72 @param algo(unicode): one of the XEP_300.ALGOS keys
69 if you want to hash a big file 73 @return (hash object): same object s in hashlib.
70 @param algo(unicode): one of the XEP_300.ALGOS keys 74 update method need to be called for each chunh
71 @return (hash object): same object s in hashlib. 75 diget or hexdigest can be used at the end
72 update method need to be called for each chunh
73 diget or hexdigest can be used at the end
74 """ 76 """
75 return self.ALGOS[algo]() 77 return self.ALGOS[algo]()
78
79 def getDefaultAlgo(self):
80 return ALGO_DEFAULT
76 81
77 @defer.inlineCallbacks 82 @defer.inlineCallbacks
78 def getBestPeerAlgo(self, to_jid, profile): 83 def getBestPeerAlgo(self, to_jid, profile):
79 """Return the best available hashing algorith of other peer 84 """Return the best available hashing algorith of other peer
80 85
90 log.debug(u"Best hashing algorithm found for {jid}: {algo}".format( 95 log.debug(u"Best hashing algorithm found for {jid}: {algo}".format(
91 jid=to_jid.full(), 96 jid=to_jid.full(),
92 algo=algo)) 97 algo=algo))
93 defer.returnValue(algo) 98 defer.returnValue(algo)
94 99
95 def calculateHashBlocking(self, file_obj, hasher): 100 def _calculateHashBlocking(self, file_obj, hasher):
96 """Calculate hash in a blocking way 101 """Calculate hash in a blocking way
97 102
103 /!\\ blocking method, please use calculateHash instead
98 @param file_obj(file): a file-like object 104 @param file_obj(file): a file-like object
99 @param hasher(callable): the method to call to initialise hash object 105 @param hasher(callable): the method to call to initialise hash object
100 @return (str): the hex digest of the hash 106 @return (str): the hex digest of the hash
101 """ 107 """
102 hash_ = hasher() 108 hash_ = hasher()
105 if not buf: 111 if not buf:
106 break 112 break
107 hash_.update(buf) 113 hash_.update(buf)
108 return hash_.hexdigest() 114 return hash_.hexdigest()
109 115
116 def calculateHash(self, file_obj, hasher):
117 return threads.deferToThread(self._calculateHashBlocking, file_obj, hasher)
118
110 def calculateHashElt(self, file_obj=None, algo=ALGO_DEFAULT): 119 def calculateHashElt(self, file_obj=None, algo=ALGO_DEFAULT):
111 """Compute hash and build hash element 120 """Compute hash and build hash element
112 121
113 @param file_obj(file, None): file-like object to use to calculate the hash 122 @param file_obj(file, None): file-like object to use to calculate the hash
114 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS 123 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS
115 @return (D(domish.Element)): hash element 124 @return (D(domish.Element)): hash element
116 """ 125 """
117 def hashCalculated(hash_): 126 def hashCalculated(hash_):
118 return self.buildHashElt(hash_, algo) 127 return self.buildHashElt(hash_, algo)
119 hasher = self.ALGOS[algo] 128 hasher = self.ALGOS[algo]
120 hash_d = threads.deferToThread(self.calculateHashBlocking, file_obj, hasher) 129 hash_d = self.calculateHash(file_obj, hasher)
121 hash_d.addCallback(hashCalculated) 130 hash_d.addCallback(hashCalculated)
122 return hash_d 131 return hash_d
123 132
124 def buildHashElt(self, hash_=None, algo=ALGO_DEFAULT): 133 def buildHashUsedElt(self, algo=ALGO_DEFAULT):
134 hash_used_elt = domish.Element((NS_HASHES, 'hash-used'))
135 hash_used_elt['algo'] = algo
136 return hash_used_elt
137
138 def parseHashUsedElt(self, parent):
139 """Find and parse a hash-used element
140
141 @param (domish.Element): parent of <hash/> element
142 @return (unicode): hash algorithm used
143 @raise exceptions.NotFound: the element is not present
144 @raise exceptions.DataError: the element is invalid
145 """
146 try:
147 hash_used_elt = next(parent.elements(NS_HASHES, 'hash-used'))
148 except StopIteration:
149 raise exceptions.NotFound
150 algo = hash_used_elt[u'algo']
151 if not algo:
152 raise exceptions.DataError
153 return algo
154
155 def buildHashElt(self, hash_, algo=ALGO_DEFAULT):
125 """Compute hash and build hash element 156 """Compute hash and build hash element
126 157
127 @param hash_(None, str): hash to use, or None for an empty element 158 @param hash_(str): hash to use
128 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS 159 @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS
129 @return (domish.Element): computed hash 160 @return (domish.Element): computed hash
130 """ 161 """
162 assert hash_
163 assert algo
131 hash_elt = domish.Element((NS_HASHES, 'hash')) 164 hash_elt = domish.Element((NS_HASHES, 'hash'))
132 if hash_ is not None: 165 if hash_ is not None:
133 hash_elt.addContent(hash_) 166 hash_elt.addContent(base64.b64encode(hash_))
134 hash_elt['algo']=algo 167 hash_elt['algo'] = algo
135 return hash_elt 168 return hash_elt
136 169
137 def parseHashElt(self, parent): 170 def parseHashElt(self, parent):
138 """Find and parse a hash element 171 """Find and parse a hash element
139 172
140 if multiple elements are found, the strongest managed one is returned 173 if multiple elements are found, the strongest managed one is returned
141 @param (domish.Element): parent of <hash/> element 174 @param (domish.Element): parent of <hash/> element
142 @return (tuple[unicode, str]): (algo, hash) tuple 175 @return (tuple[unicode, str]): (algo, hash) tuple
143 both values can be None if <hash/> is empty 176 both values can be None if <hash/> is empty
144 @raise exceptions.NotFound: the element is not present 177 @raise exceptions.NotFound: the element is not present
178 @raise exceptions.DataError: the element is invalid
145 """ 179 """
146 algos = XEP_0300.ALGOS.keys() 180 algos = XEP_0300.ALGOS.keys()
147 hash_elt = None 181 hash_elt = None
148 best_algo = None 182 best_algo = None
149 best_value = None 183 best_value = None
156 algo = None 190 algo = None
157 continue 191 continue
158 192
159 if best_algo is None or algos.index(best_algo) < idx: 193 if best_algo is None or algos.index(best_algo) < idx:
160 best_algo = algo 194 best_algo = algo
161 best_value = str(hash_elt) or None 195 best_value = base64.b64decode(unicode(hash_elt))
162 196
163 if not hash_elt: 197 if not hash_elt:
164 raise exceptions.NotFound 198 raise exceptions.NotFound
199 if not best_algo or not best_value:
200 raise exceptions.DataError
165 return best_algo, best_value 201 return best_algo, best_value
166 202
167 203
168 class XEP_0300_handler(XMPPHandler): 204 class XEP_0300_handler(XMPPHandler):
169 implements(iwokkel.IDisco) 205 implements(iwokkel.IDisco)