comparison sat/plugins/plugin_xep_0234.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 69e4716d6268
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for Jingle File Transfer (XEP-0234) 4 # SAT plugin for Jingle File Transfer (XEP-0234)
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23 23
24 log = getLogger(__name__) 24 log = getLogger(__name__)
25 from sat.core import exceptions 25 from sat.core import exceptions
26 from wokkel import disco, iwokkel 26 from wokkel import disco, iwokkel
27 from zope.interface import implements 27 from zope.interface import implementer
28 from sat.tools import utils 28 from sat.tools import utils
29 from sat.tools import stream 29 from sat.tools import stream
30 from sat.tools.common import date_utils 30 from sat.tools.common import date_utils
31 import os.path 31 import os.path
32 from twisted.words.xish import domish 32 from twisted.words.xish import domish
53 C.PI_MAIN: "XEP_0234", 53 C.PI_MAIN: "XEP_0234",
54 C.PI_HANDLER: "yes", 54 C.PI_HANDLER: "yes",
55 C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer"""), 55 C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer"""),
56 } 56 }
57 57
58 EXTRA_ALLOWED = {u"path", u"namespace", u"file_desc", u"file_hash"} 58 EXTRA_ALLOWED = {"path", "namespace", "file_desc", "file_hash"}
59 Range = namedtuple("Range", ("offset", "length")) 59 Range = namedtuple("Range", ("offset", "length"))
60 60
61 61
62 class XEP_0234(object): 62 class XEP_0234(object):
63 # TODO: assure everything is closed when file is sent or session terminate is received 63 # TODO: assure everything is closed when file is sent or session terminate is received
70 host.registerNamespace("jingle-ft", NS_JINGLE_FT) 70 host.registerNamespace("jingle-ft", NS_JINGLE_FT)
71 self._j = host.plugins["XEP-0166"] # shortcut to access jingle 71 self._j = host.plugins["XEP-0166"] # shortcut to access jingle
72 self._j.registerApplication(NS_JINGLE_FT, self) 72 self._j.registerApplication(NS_JINGLE_FT, self)
73 self._f = host.plugins["FILE"] 73 self._f = host.plugins["FILE"]
74 self._f.register( 74 self._f.register(
75 NS_JINGLE_FT, self.fileJingleSend, priority=10000, method_name=u"Jingle" 75 NS_JINGLE_FT, self.fileJingleSend, priority=10000, method_name="Jingle"
76 ) 76 )
77 self._hash = self.host.plugins["XEP-0300"] 77 self._hash = self.host.plugins["XEP-0300"]
78 host.bridge.addMethod( 78 host.bridge.addMethod(
79 "fileJingleSend", 79 "fileJingleSend",
80 ".plugin", 80 ".plugin",
81 in_sign="ssssa{ss}s", 81 in_sign="ssssa{ss}s",
82 out_sign="", 82 out_sign="",
83 method=self._fileJingleSend, 83 method=self._fileJingleSend,
84 async=True, 84 async_=True,
85 ) 85 )
86 host.bridge.addMethod( 86 host.bridge.addMethod(
87 "fileJingleRequest", 87 "fileJingleRequest",
88 ".plugin", 88 ".plugin",
89 in_sign="sssssa{ss}s", 89 in_sign="sssssa{ss}s",
90 out_sign="s", 90 out_sign="s",
91 method=self._fileJingleRequest, 91 method=self._fileJingleRequest,
92 async=True, 92 async_=True,
93 ) 93 )
94 94
95 def getHandler(self, client): 95 def getHandler(self, client):
96 return XEP_0234_handler() 96 return XEP_0234_handler()
97 97
100 100
101 @param session(dict): jingle session 101 @param session(dict): jingle session
102 @param content_name(unicode): name of the content 102 @param content_name(unicode): name of the content
103 @return (unicode): unique progress id 103 @return (unicode): unique progress id
104 """ 104 """
105 return u"{}_{}".format(session["id"], content_name) 105 return "{}_{}".format(session["id"], content_name)
106 106
107 # generic methods 107 # generic methods
108 108
109 def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None, 109 def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None,
110 mime_type=None, desc=None, modified=None, transfer_range=None, path=None, 110 mime_type=None, desc=None, modified=None, transfer_range=None, path=None,
125 @param **kwargs: data for plugin extension (ignored by default) 125 @param **kwargs: data for plugin extension (ignored by default)
126 @return (domish.Element): generated element 126 @return (domish.Element): generated element
127 @trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add 127 @trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add
128 """ 128 """
129 if file_elt is None: 129 if file_elt is None:
130 file_elt = domish.Element((NS_JINGLE_FT, u"file")) 130 file_elt = domish.Element((NS_JINGLE_FT, "file"))
131 for name, value in ( 131 for name, value in (
132 (u"name", name), 132 ("name", name),
133 (u"size", size), 133 ("size", size),
134 ("media-type", mime_type), 134 ("media-type", mime_type),
135 (u"desc", desc), 135 ("desc", desc),
136 (u"path", path), 136 ("path", path),
137 (u"namespace", namespace), 137 ("namespace", namespace),
138 ): 138 ):
139 if value is not None: 139 if value is not None:
140 file_elt.addElement(name, content=unicode(value)) 140 file_elt.addElement(name, content=str(value))
141 141
142 if modified is not None: 142 if modified is not None:
143 if isinstance(modified, int): 143 if isinstance(modified, int):
144 file_elt.addElement(u"date", utils.xmpp_date(modified or None)) 144 file_elt.addElement("date", utils.xmpp_date(modified or None))
145 else: 145 else:
146 file_elt.addElement(u"date", modified) 146 file_elt.addElement("date", modified)
147 elif "created" in kwargs: 147 elif "created" in kwargs:
148 file_elt.addElement(u"date", utils.xmpp_date(kwargs.pop("created"))) 148 file_elt.addElement("date", utils.xmpp_date(kwargs.pop("created")))
149 149
150 range_elt = file_elt.addElement(u"range") 150 range_elt = file_elt.addElement("range")
151 if transfer_range is not None: 151 if transfer_range is not None:
152 if transfer_range.offset is not None: 152 if transfer_range.offset is not None:
153 range_elt[u"offset"] = transfer_range.offset 153 range_elt["offset"] = transfer_range.offset
154 if transfer_range.length is not None: 154 if transfer_range.length is not None:
155 range_elt[u"length"] = transfer_range.length 155 range_elt["length"] = transfer_range.length
156 if file_hash is not None: 156 if file_hash is not None:
157 if not file_hash: 157 if not file_hash:
158 file_elt.addChild(self._hash.buildHashUsedElt()) 158 file_elt.addChild(self._hash.buildHashUsedElt())
159 else: 159 else:
160 file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo)) 160 file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo))
161 elif hash_algo is not None: 161 elif hash_algo is not None:
162 file_elt.addChild(self._hash.buildHashUsedElt(hash_algo)) 162 file_elt.addChild(self._hash.buildHashUsedElt(hash_algo))
163 self.host.trigger.point(u"XEP-0234_buildFileElement", file_elt, extra_args=kwargs) 163 self.host.trigger.point("XEP-0234_buildFileElement", file_elt, extra_args=kwargs)
164 if kwargs: 164 if kwargs:
165 for kw in kwargs: 165 for kw in kwargs:
166 log.debug("ignored keyword: {}".format(kw)) 166 log.debug("ignored keyword: {}".format(kw))
167 return file_elt 167 return file_elt
168 168
202 @raise exceptions.DataError: if file_elt uri is not NS_JINGLE_FT 202 @raise exceptions.DataError: if file_elt uri is not NS_JINGLE_FT
203 """ 203 """
204 if parent_elt is not None: 204 if parent_elt is not None:
205 if file_elt is not None: 205 if file_elt is not None:
206 raise exceptions.InternalError( 206 raise exceptions.InternalError(
207 u"file_elt must be None if parent_elt is set" 207 "file_elt must be None if parent_elt is set"
208 ) 208 )
209 try: 209 try:
210 file_elt = next(parent_elt.elements(NS_JINGLE_FT, u"file")) 210 file_elt = next(parent_elt.elements(NS_JINGLE_FT, "file"))
211 except StopIteration: 211 except StopIteration:
212 raise exceptions.NotFound() 212 raise exceptions.NotFound()
213 else: 213 else:
214 if not file_elt or file_elt.uri != NS_JINGLE_FT: 214 if not file_elt or file_elt.uri != NS_JINGLE_FT:
215 raise exceptions.DataError( 215 raise exceptions.DataError(
216 u"invalid <file> element: {stanza}".format(stanza=file_elt.toXml()) 216 "invalid <file> element: {stanza}".format(stanza=file_elt.toXml())
217 ) 217 )
218 218
219 if file_data is None: 219 if file_data is None:
220 file_data = {} 220 file_data = {}
221 221
222 for name in (u"name", u"desc", u"path", u"namespace"): 222 for name in ("name", "desc", "path", "namespace"):
223 try: 223 try:
224 file_data[name] = unicode(next(file_elt.elements(NS_JINGLE_FT, name))) 224 file_data[name] = str(next(file_elt.elements(NS_JINGLE_FT, name)))
225 except StopIteration: 225 except StopIteration:
226 pass 226 pass
227 227
228 name = file_data.get(u"name") 228 name = file_data.get("name")
229 if name == u"..": 229 if name == "..":
230 # we don't want to go to parent dir when joining to a path 230 # we don't want to go to parent dir when joining to a path
231 name = u"--" 231 name = "--"
232 file_data[u"name"] = name 232 file_data["name"] = name
233 elif name is not None and u"/" in name or u"\\" in name: 233 elif name is not None and "/" in name or "\\" in name:
234 file_data[u"name"] = regex.pathEscape(name) 234 file_data["name"] = regex.pathEscape(name)
235 235
236 try: 236 try:
237 file_data[u"mime_type"] = unicode( 237 file_data["mime_type"] = str(
238 next(file_elt.elements(NS_JINGLE_FT, u"media-type")) 238 next(file_elt.elements(NS_JINGLE_FT, "media-type"))
239 ) 239 )
240 except StopIteration: 240 except StopIteration:
241 pass 241 pass
242 242
243 try: 243 try:
244 file_data[u"size"] = int( 244 file_data["size"] = int(
245 unicode(next(file_elt.elements(NS_JINGLE_FT, u"size"))) 245 str(next(file_elt.elements(NS_JINGLE_FT, "size")))
246 ) 246 )
247 except StopIteration: 247 except StopIteration:
248 pass 248 pass
249 249
250 try: 250 try:
251 file_data[u"modified"] = date_utils.date_parse( 251 file_data["modified"] = date_utils.date_parse(
252 next(file_elt.elements(NS_JINGLE_FT, u"date")) 252 next(file_elt.elements(NS_JINGLE_FT, "date"))
253 ) 253 )
254 except StopIteration: 254 except StopIteration:
255 pass 255 pass
256 256
257 try: 257 try:
258 range_elt = file_elt.elements(NS_JINGLE_FT, u"range").next() 258 range_elt = next(file_elt.elements(NS_JINGLE_FT, "range"))
259 except StopIteration: 259 except StopIteration:
260 pass 260 pass
261 else: 261 else:
262 offset = range_elt.getAttribute("offset") 262 offset = range_elt.getAttribute("offset")
263 length = range_elt.getAttribute("length") 263 length = range_elt.getAttribute("length")
264 if offset or length or keep_empty_range: 264 if offset or length or keep_empty_range:
265 file_data[u"transfer_range"] = Range(offset=offset, length=length) 265 file_data["transfer_range"] = Range(offset=offset, length=length)
266 266
267 prefix = u"given_" if given else u"" 267 prefix = "given_" if given else ""
268 hash_algo_key, hash_key = u"hash_algo", prefix + u"file_hash" 268 hash_algo_key, hash_key = "hash_algo", prefix + "file_hash"
269 try: 269 try:
270 file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt( 270 file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt(
271 file_elt 271 file_elt
272 ) 272 )
273 except exceptions.NotFound: 273 except exceptions.NotFound:
274 pass 274 pass
275 275
276 self.host.trigger.point(u"XEP-0234_parseFileElement", file_elt, file_data) 276 self.host.trigger.point("XEP-0234_parseFileElement", file_elt, file_data)
277 277
278 return file_data 278 return file_data
279 279
280 # bridge methods 280 # bridge methods
281 281
377 progress_id_d = defer.Deferred() 377 progress_id_d = defer.Deferred()
378 if extra is None: 378 if extra is None:
379 extra = {} 379 extra = {}
380 if file_hash is not None: 380 if file_hash is not None:
381 if hash_algo is None: 381 if hash_algo is None:
382 raise ValueError(_(u"hash_algo must be set if file_hash is set")) 382 raise ValueError(_("hash_algo must be set if file_hash is set"))
383 extra["file_hash"] = file_hash 383 extra["file_hash"] = file_hash
384 extra["hash_algo"] = hash_algo 384 extra["hash_algo"] = hash_algo
385 else: 385 else:
386 if hash_algo is not None: 386 if hash_algo is not None:
387 raise ValueError(_(u"file_hash must be set if hash_algo is set")) 387 raise ValueError(_("file_hash must be set if hash_algo is set"))
388 yield self._j.initiate( 388 yield self._j.initiate(
389 client, 389 client,
390 peer_jid, 390 peer_jid,
391 [ 391 [
392 { 392 {
412 if extra is None: 412 if extra is None:
413 extra = {} 413 extra = {}
414 else: 414 else:
415 if not EXTRA_ALLOWED.issuperset(extra): 415 if not EXTRA_ALLOWED.issuperset(extra):
416 raise ValueError( 416 raise ValueError(
417 _(u"only the following keys are allowed in extra: {keys}").format( 417 _("only the following keys are allowed in extra: {keys}").format(
418 keys=u", ".join(EXTRA_ALLOWED) 418 keys=", ".join(EXTRA_ALLOWED)
419 ) 419 )
420 ) 420 )
421 progress_id_d.callback(self.getProgressId(session, content_name)) 421 progress_id_d.callback(self.getProgressId(session, content_name))
422 content_data = session["contents"][content_name] 422 content_data = session["contents"][content_name]
423 application_data = content_data["application_data"] 423 application_data = content_data["application_data"]
425 application_data["file_path"] = filepath 425 application_data["file_path"] = filepath
426 file_data = application_data["file_data"] = {} 426 file_data = application_data["file_data"] = {}
427 desc_elt = domish.Element((NS_JINGLE_FT, "description")) 427 desc_elt = domish.Element((NS_JINGLE_FT, "description"))
428 file_elt = desc_elt.addElement("file") 428 file_elt = desc_elt.addElement("file")
429 429
430 if content_data[u"senders"] == self._j.ROLE_INITIATOR: 430 if content_data["senders"] == self._j.ROLE_INITIATOR:
431 # we send a file 431 # we send a file
432 if name is None: 432 if name is None:
433 name = os.path.basename(filepath) 433 name = os.path.basename(filepath)
434 file_data[u"date"] = utils.xmpp_date() 434 file_data["date"] = utils.xmpp_date()
435 file_data[u"desc"] = extra.pop(u"file_desc", u"") 435 file_data["desc"] = extra.pop("file_desc", "")
436 file_data[u"name"] = name 436 file_data["name"] = name
437 mime_type = mimetypes.guess_type(name, strict=False)[0] 437 mime_type = mimetypes.guess_type(name, strict=False)[0]
438 if mime_type is not None: 438 if mime_type is not None:
439 file_data[u"mime_type"] = mime_type 439 file_data["mime_type"] = mime_type
440 file_data[u"size"] = os.path.getsize(filepath) 440 file_data["size"] = os.path.getsize(filepath)
441 if u"namespace" in extra: 441 if "namespace" in extra:
442 file_data[u"namespace"] = extra[u"namespace"] 442 file_data["namespace"] = extra["namespace"]
443 if u"path" in extra: 443 if "path" in extra:
444 file_data[u"path"] = extra[u"path"] 444 file_data["path"] = extra["path"]
445 self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u"") 445 self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash="")
446 else: 446 else:
447 # we request a file 447 # we request a file
448 file_hash = extra.pop(u"file_hash", u"") 448 file_hash = extra.pop("file_hash", "")
449 if not name and not file_hash: 449 if not name and not file_hash:
450 raise ValueError(_(u"you need to provide at least name or file hash")) 450 raise ValueError(_("you need to provide at least name or file hash"))
451 if name: 451 if name:
452 file_data[u"name"] = name 452 file_data["name"] = name
453 if file_hash: 453 if file_hash:
454 file_data[u"file_hash"] = file_hash 454 file_data["file_hash"] = file_hash
455 file_data[u"hash_algo"] = extra[u"hash_algo"] 455 file_data["hash_algo"] = extra["hash_algo"]
456 else: 456 else:
457 file_data[u"hash_algo"] = self._hash.getDefaultAlgo() 457 file_data["hash_algo"] = self._hash.getDefaultAlgo()
458 if u"namespace" in extra: 458 if "namespace" in extra:
459 file_data[u"namespace"] = extra[u"namespace"] 459 file_data["namespace"] = extra["namespace"]
460 if u"path" in extra: 460 if "path" in extra:
461 file_data[u"path"] = extra[u"path"] 461 file_data["path"] = extra["path"]
462 self.buildFileElementFromDict(file_data, file_elt=file_elt) 462 self.buildFileElementFromDict(file_data, file_elt=file_elt)
463 463
464 return desc_elt 464 return desc_elt
465 465
466 def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt): 466 def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt):
467 """This method request confirmation for a jingle session""" 467 """This method request confirmation for a jingle session"""
468 content_data = session["contents"][content_name] 468 content_data = session["contents"][content_name]
469 senders = content_data[u"senders"] 469 senders = content_data["senders"]
470 if senders not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER): 470 if senders not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER):
471 log.warning(u"Bad sender, assuming initiator") 471 log.warning("Bad sender, assuming initiator")
472 senders = content_data[u"senders"] = self._j.ROLE_INITIATOR 472 senders = content_data["senders"] = self._j.ROLE_INITIATOR
473 # first we grab file informations 473 # first we grab file informations
474 try: 474 try:
475 file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next() 475 file_elt = next(desc_elt.elements(NS_JINGLE_FT, "file"))
476 except StopIteration: 476 except StopIteration:
477 raise failure.Failure(exceptions.DataError) 477 raise failure.Failure(exceptions.DataError)
478 file_data = {"progress_id": self.getProgressId(session, content_name)} 478 file_data = {"progress_id": self.getProgressId(session, content_name)}
479 479
480 if senders == self._j.ROLE_RESPONDER: 480 if senders == self._j.ROLE_RESPONDER:
514 finished_d.addCallbacks( 514 finished_d.addCallbacks(
515 self._finishedCb, self._finishedEb, args, None, args 515 self._finishedCb, self._finishedEb, args, None, args
516 ) 516 )
517 defer.returnValue(confirmed) 517 defer.returnValue(confirmed)
518 518
519 log.warning(_(u"File continue is not implemented yet")) 519 log.warning(_("File continue is not implemented yet"))
520 defer.returnValue(False) 520 defer.returnValue(False)
521 521
522 def _fileReceivingRequestConf( 522 def _fileReceivingRequestConf(
523 self, client, session, content_data, content_name, file_data, file_elt 523 self, client, session, content_data, content_name, file_data, file_elt
524 ): 524 ):
543 raise failure.Failure(exceptions.DataError) 543 raise failure.Failure(exceptions.DataError)
544 544
545 name = file_data["name"] 545 name = file_data["name"]
546 if "/" in name or "\\" in name: 546 if "/" in name or "\\" in name:
547 log.warning( 547 log.warning(
548 u"File name contain path characters, we replace them: {}".format(name) 548 "File name contain path characters, we replace them: {}".format(name)
549 ) 549 )
550 file_data["name"] = name.replace("/", "_").replace("\\", "_") 550 file_data["name"] = name.replace("/", "_").replace("\\", "_")
551 551
552 content_data["application_data"]["file_data"] = file_data 552 content_data["application_data"]["file_data"] = file_data
553 553
572 content_data = session["contents"][content_name] 572 content_data = session["contents"][content_name]
573 application_data = content_data["application_data"] 573 application_data = content_data["application_data"]
574 if action in (self._j.A_ACCEPTED_ACK,): 574 if action in (self._j.A_ACCEPTED_ACK,):
575 pass 575 pass
576 elif action == self._j.A_SESSION_INITIATE: 576 elif action == self._j.A_SESSION_INITIATE:
577 file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next() 577 file_elt = next(desc_elt.elements(NS_JINGLE_FT, "file"))
578 try: 578 try:
579 file_elt.elements(NS_JINGLE_FT, "range").next() 579 next(file_elt.elements(NS_JINGLE_FT, "range"))
580 except StopIteration: 580 except StopIteration:
581 # initiator doesn't manage <range>, but we do so we advertise it 581 # initiator doesn't manage <range>, but we do so we advertise it
582 #  FIXME: to be checked 582 #  FIXME: to be checked
583 log.debug("adding <range> element") 583 log.debug("adding <range> element")
584 file_elt.addElement("range") 584 file_elt.addElement("range")
585 elif action == self._j.A_SESSION_ACCEPT: 585 elif action == self._j.A_SESSION_ACCEPT:
586 assert not "stream_object" in content_data 586 assert not "stream_object" in content_data
587 file_data = application_data["file_data"] 587 file_data = application_data["file_data"]
588 file_path = application_data["file_path"] 588 file_path = application_data["file_path"]
589 senders = content_data[u"senders"] 589 senders = content_data["senders"]
590 if senders != session[u"role"]: 590 if senders != session["role"]:
591 # we are receiving the file 591 # we are receiving the file
592 try: 592 try:
593 # did the responder specified the size of the file? 593 # did the responder specified the size of the file?
594 file_elt = next(desc_elt.elements(NS_JINGLE_FT, u"file")) 594 file_elt = next(desc_elt.elements(NS_JINGLE_FT, "file"))
595 size_elt = next(file_elt.elements(NS_JINGLE_FT, u"size")) 595 size_elt = next(file_elt.elements(NS_JINGLE_FT, "size"))
596 size = int(unicode(size_elt)) 596 size = int(str(size_elt))
597 except (StopIteration, ValueError): 597 except (StopIteration, ValueError):
598 size = None 598 size = None
599 # XXX: hash security is not critical here, so we just take the higher mandatory one 599 # XXX: hash security is not critical here, so we just take the higher mandatory one
600 hasher = file_data["hash_hasher"] = self._hash.getHasher() 600 hasher = file_data["hash_hasher"] = self._hash.getHasher()
601 content_data["stream_object"] = stream.FileStreamObject( 601 content_data["stream_object"] = stream.FileStreamObject(
622 ) 622 )
623 finished_d = content_data["finished_d"] = defer.Deferred() 623 finished_d = content_data["finished_d"] = defer.Deferred()
624 args = [client, session, content_name, content_data] 624 args = [client, session, content_name, content_data]
625 finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) 625 finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
626 else: 626 else:
627 log.warning(u"FIXME: unmanaged action {}".format(action)) 627 log.warning("FIXME: unmanaged action {}".format(action))
628 return desc_elt 628 return desc_elt
629 629
630 def jingleSessionInfo(self, client, action, session, content_name, jingle_elt): 630 def jingleSessionInfo(self, client, action, session, content_name, jingle_elt):
631 """Called on session-info action 631 """Called on session-info action
632 632
642 pass 642 pass
643 elif elt.name == "checksum": 643 elif elt.name == "checksum":
644 # we have received the file hash, we need to parse it 644 # we have received the file hash, we need to parse it
645 if content_data["senders"] == session["role"]: 645 if content_data["senders"] == session["role"]:
646 log.warning( 646 log.warning(
647 u"unexpected checksum received while we are the file sender" 647 "unexpected checksum received while we are the file sender"
648 ) 648 )
649 raise exceptions.DataError 649 raise exceptions.DataError
650 info_content_name = elt["name"] 650 info_content_name = elt["name"]
651 if info_content_name != content_name: 651 if info_content_name != content_name:
652 # it was for an other content... 652 # it was for an other content...
653 return 653 return
654 file_data = content_data["application_data"]["file_data"] 654 file_data = content_data["application_data"]["file_data"]
655 try: 655 try:
656 file_elt = elt.elements((NS_JINGLE_FT, "file")).next() 656 file_elt = next(elt.elements((NS_JINGLE_FT, "file")))
657 except StopIteration: 657 except StopIteration:
658 raise exceptions.DataError 658 raise exceptions.DataError
659 algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) 659 algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt)
660 if algo != file_data.get("hash_algo"): 660 if algo != file_data.get("hash_algo"):
661 log.warning( 661 log.warning(
662 u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format( 662 "Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format(
663 peer_algo=algo, 663 peer_algo=algo,
664 our_algo=file_data.get("hash_algo"), 664 our_algo=file_data.get("hash_algo"),
665 profile=client.profile, 665 profile=client.profile,
666 ) 666 )
667 ) 667 )
683 def _sendCheckSum(self, client, session, content_name, content_data): 683 def _sendCheckSum(self, client, session, content_name, content_data):
684 """Send the session-info with the hash checksum""" 684 """Send the session-info with the hash checksum"""
685 file_data = content_data["application_data"]["file_data"] 685 file_data = content_data["application_data"]["file_data"]
686 hasher = file_data["hash_hasher"] 686 hasher = file_data["hash_hasher"]
687 hash_ = hasher.hexdigest() 687 hash_ = hasher.hexdigest()
688 log.debug(u"Calculated hash: {}".format(hash_)) 688 log.debug("Calculated hash: {}".format(hash_))
689 iq_elt, jingle_elt = self._j.buildSessionInfo(client, session) 689 iq_elt, jingle_elt = self._j.buildSessionInfo(client, session)
690 checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum")) 690 checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum"))
691 checksum_elt["creator"] = content_data["creator"] 691 checksum_elt["creator"] = content_data["creator"]
692 checksum_elt["name"] = content_name 692 checksum_elt["name"] = content_name
693 file_elt = checksum_elt.addElement("file") 693 file_elt = checksum_elt.addElement("file")
710 file_data = content_data["application_data"]["file_data"] 710 file_data = content_data["application_data"]["file_data"]
711 given_hash = file_data.get("given_file_hash") 711 given_hash = file_data.get("given_file_hash")
712 if given_hash is None: 712 if given_hash is None:
713 if last_try: 713 if last_try:
714 log.warning( 714 log.warning(
715 u"sender didn't sent hash checksum, we can't check the file [{profile}]".format( 715 "sender didn't sent hash checksum, we can't check the file [{profile}]".format(
716 profile=client.profile 716 profile=client.profile
717 ) 717 )
718 ) 718 )
719 self._j.delayedContentTerminate(client, session, content_name) 719 self._j.delayedContentTerminate(client, session, content_name)
720 content_data["stream_object"].close() 720 content_data["stream_object"].close()
721 return True 721 return True
722 return False 722 return False
723 hasher = file_data["hash_hasher"] 723 hasher = file_data["hash_hasher"]
724 hash_ = hasher.hexdigest() 724 hash_ = hasher.hexdigest().encode('utf-8')
725 725
726 if hash_ == given_hash: 726 if hash_ == given_hash:
727 log.info(u"Hash checked, file was successfully transfered: {}".format(hash_)) 727 log.info("Hash checked, file was successfully transfered: {}".format(hash_))
728 progress_metadata = { 728 progress_metadata = {
729 "hash": hash_, 729 "hash": hash_,
730 "hash_algo": file_data["hash_algo"], 730 "hash_algo": file_data["hash_algo"],
731 "hash_verified": C.BOOL_TRUE, 731 "hash_verified": C.BOOL_TRUE,
732 } 732 }
733 error = None 733 error = None
734 else: 734 else:
735 log.warning(u"Hash mismatch, the file was not transfered correctly") 735 log.warning("Hash mismatch, the file was not transfered correctly")
736 progress_metadata = None 736 progress_metadata = None
737 error = u"Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format( 737 error = "Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format(
738 algo=file_data["hash_algo"], given=given_hash, our=hash_ 738 algo=file_data["hash_algo"], given=given_hash, our=hash_
739 ) 739 )
740 740
741 self._j.delayedContentTerminate(client, session, content_name) 741 self._j.delayedContentTerminate(client, session, content_name)
742 content_data["stream_object"].close(progress_metadata, error) 742 content_data["stream_object"].close(progress_metadata, error)
746 except (KeyError, internet_error.AlreadyCalled): 746 except (KeyError, internet_error.AlreadyCalled):
747 pass 747 pass
748 return True 748 return True
749 749
750 def _finishedCb(self, __, client, session, content_name, content_data): 750 def _finishedCb(self, __, client, session, content_name, content_data):
751 log.info(u"File transfer terminated") 751 log.info("File transfer terminated")
752 if content_data["senders"] != session["role"]: 752 if content_data["senders"] != session["role"]:
753 # we terminate the session only if we are the receiver, 753 # we terminate the session only if we are the receiver,
754 # as recommanded in XEP-0234 §2 (after example 6) 754 # as recommanded in XEP-0234 §2 (after example 6)
755 content_data["transfer_finished"] = True 755 content_data["transfer_finished"] = True
756 if not self._receiverTryTerminate( 756 if not self._receiverTryTerminate(
770 # we are the sender, we send the checksum 770 # we are the sender, we send the checksum
771 self._sendCheckSum(client, session, content_name, content_data) 771 self._sendCheckSum(client, session, content_name, content_data)
772 content_data["stream_object"].close() 772 content_data["stream_object"].close()
773 773
774 def _finishedEb(self, failure, client, session, content_name, content_data): 774 def _finishedEb(self, failure, client, session, content_name, content_data):
775 log.warning(u"Error while streaming file: {}".format(failure)) 775 log.warning("Error while streaming file: {}".format(failure))
776 content_data["stream_object"].close() 776 content_data["stream_object"].close()
777 self._j.contentTerminate( 777 self._j.contentTerminate(
778 client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT 778 client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT
779 ) 779 )
780 780
781 781
782 @implementer(iwokkel.IDisco)
782 class XEP_0234_handler(XMPPHandler): 783 class XEP_0234_handler(XMPPHandler):
783 implements(iwokkel.IDisco)
784 784
785 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 785 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
786 return [disco.DiscoFeature(NS_JINGLE_FT)] 786 return [disco.DiscoFeature(NS_JINGLE_FT)]
787 787
788 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 788 def getDiscoItems(self, requestor, target, nodeIdentifier=""):