comparison sat/plugins/plugin_xep_0329.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 db0890c9c7db
children 95e2fd14a761
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 File Information Sharing (XEP-0329) 4 # SAT plugin for File Information Sharing (XEP-0329)
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
24 24
25 log = getLogger(__name__) 25 log = getLogger(__name__)
26 from sat.tools import stream 26 from sat.tools import stream
27 from sat.tools.common import regex 27 from sat.tools.common import regex
28 from wokkel import disco, iwokkel 28 from wokkel import disco, iwokkel
29 from zope.interface import implements 29 from zope.interface import implementer
30 from twisted.words.protocols.jabber import xmlstream 30 from twisted.words.protocols.jabber import xmlstream
31 from twisted.words.protocols.jabber import jid 31 from twisted.words.protocols.jabber import jid
32 from twisted.words.protocols.jabber import error as jabber_error 32 from twisted.words.protocols.jabber import error as jabber_error
33 from twisted.internet import defer 33 from twisted.internet import defer
34 import mimetypes 34 import mimetypes
43 C.PI_MODES: C.PLUG_MODE_BOTH, 43 C.PI_MODES: C.PLUG_MODE_BOTH,
44 C.PI_PROTOCOLS: ["XEP-0329"], 44 C.PI_PROTOCOLS: ["XEP-0329"],
45 C.PI_DEPENDENCIES: ["XEP-0234", "XEP-0300", "XEP-0106"], 45 C.PI_DEPENDENCIES: ["XEP-0234", "XEP-0300", "XEP-0106"],
46 C.PI_MAIN: "XEP_0329", 46 C.PI_MAIN: "XEP_0329",
47 C.PI_HANDLER: "yes", 47 C.PI_HANDLER: "yes",
48 C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing"""), 48 C.PI_DESCRIPTION: _("""Implementation of File Information Sharing"""),
49 } 49 }
50 50
51 NS_FIS = "urn:xmpp:fis:0" 51 NS_FIS = "urn:xmpp:fis:0"
52 52
53 IQ_FIS_REQUEST = C.IQ_GET + '/query[@xmlns="' + NS_FIS + '"]' 53 IQ_FIS_REQUEST = C.IQ_GET + '/query[@xmlns="' + NS_FIS + '"]'
54 SINGLE_FILES_DIR = u"files" 54 SINGLE_FILES_DIR = "files"
55 TYPE_VIRTUAL = u"virtual" 55 TYPE_VIRTUAL = "virtual"
56 TYPE_PATH = u"path" 56 TYPE_PATH = "path"
57 SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) 57 SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL)
58 KEY_TYPE = u"type" 58 KEY_TYPE = "type"
59 59
60 60
61 class ShareNode(object): 61 class ShareNode(object):
62 """Node containing directory or files to share, virtual or real""" 62 """Node containing directory or files to share, virtual or real"""
63 63
64 host = None 64 host = None
65 65
66 def __init__(self, name, parent, type_, access, path=None): 66 def __init__(self, name, parent, type_, access, path=None):
67 assert type_ in SHARE_TYPES 67 assert type_ in SHARE_TYPES
68 if name is not None: 68 if name is not None:
69 if name == u".." or u"/" in name or u"\\" in name: 69 if name == ".." or "/" in name or "\\" in name:
70 log.warning( 70 log.warning(
71 _(u"path change chars found in name [{name}], hack attempt?").format( 71 _("path change chars found in name [{name}], hack attempt?").format(
72 name=name 72 name=name
73 ) 73 )
74 ) 74 )
75 if name == u"..": 75 if name == "..":
76 name = u"--" 76 name = "--"
77 else: 77 else:
78 name = regex.pathEscape(name) 78 name = regex.pathEscape(name)
79 self.name = name 79 self.name = name
80 self.children = {} 80 self.children = {}
81 self.type = type_ 81 self.type = type_
87 parent.addChild(self) 87 parent.addChild(self)
88 else: 88 else:
89 assert name is None 89 assert name is None
90 if path is not None: 90 if path is not None:
91 if type_ != TYPE_PATH: 91 if type_ != TYPE_PATH:
92 raise exceptions.InternalError(_(u"path can only be set on path nodes")) 92 raise exceptions.InternalError(_("path can only be set on path nodes"))
93 self._path = path 93 self._path = path
94 94
95 @property 95 @property
96 def path(self): 96 def path(self):
97 return self._path 97 return self._path
104 104
105 def __iter__(self): 105 def __iter__(self):
106 return self.children.__iter__() 106 return self.children.__iter__()
107 107
108 def iteritems(self): 108 def iteritems(self):
109 return self.children.iteritems() 109 return iter(self.children.items())
110 110
111 def itervalues(self): 111 def itervalues(self):
112 return self.children.itervalues() 112 return iter(self.children.values())
113 113
114 def getOrCreate(self, name, type_=TYPE_VIRTUAL, access=None): 114 def getOrCreate(self, name, type_=TYPE_VIRTUAL, access=None):
115 """Get a node or create a virtual node and return it""" 115 """Get a node or create a virtual node and return it"""
116 if access is None: 116 if access is None:
117 access = {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}} 117 access = {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}
121 node = ShareNode(name, self, type_=type_, access=access) 121 node = ShareNode(name, self, type_=type_, access=access)
122 return node 122 return node
123 123
124 def addChild(self, node): 124 def addChild(self, node):
125 if node.parent is not None: 125 if node.parent is not None:
126 raise exceptions.ConflictError(_(u"a node can't have several parents")) 126 raise exceptions.ConflictError(_("a node can't have several parents"))
127 node.parent = self 127 node.parent = self
128 self.children[node.name] = node 128 self.children[node.name] = node
129 129
130 def removeFromParent(self): 130 def removeFromParent(self):
131 try: 131 try:
132 del self.parent.children[self.name] 132 del self.parent.children[self.name]
133 except TypeError: 133 except TypeError:
134 raise exceptions.InternalError( 134 raise exceptions.InternalError(
135 u"trying to remove a node from inexisting parent" 135 "trying to remove a node from inexisting parent"
136 ) 136 )
137 except KeyError: 137 except KeyError:
138 raise exceptions.InternalError(u"node not found in parent's children") 138 raise exceptions.InternalError("node not found in parent's children")
139 self.parent = None 139 self.parent = None
140 140
141 def _checkNodePermission(self, client, node, perms, peer_jid): 141 def _checkNodePermission(self, client, node, perms, peer_jid):
142 """Check access to this node for peer_jid 142 """Check access to this node for peer_jid
143 143
144 @param node(SharedNode): node to check access 144 @param node(SharedNode): node to check access
145 @param perms(unicode): permissions to check, iterable of C.ACCESS_PERM_* 145 @param perms(unicode): permissions to check, iterable of C.ACCESS_PERM_*
146 @param peer_jid(jid.JID): entity which try to access the node 146 @param peer_jid(jid.JID): entity which try to access the node
147 @return (bool): True if entity can access 147 @return (bool): True if entity can access
148 """ 148 """
149 file_data = {u"access": self.access, u"owner": client.jid.userhostJID()} 149 file_data = {"access": self.access, "owner": client.jid.userhostJID()}
150 try: 150 try:
151 self.host.memory.checkFilePermission(file_data, peer_jid, perms) 151 self.host.memory.checkFilePermission(file_data, peer_jid, perms)
152 except exceptions.PermissionError: 152 except exceptions.PermissionError:
153 return False 153 return False
154 else: 154 else:
187 @return (dict, unicode): shared data, remaining path 187 @return (dict, unicode): shared data, remaining path
188 @raise exceptions.PermissionError: user can't access this file 188 @raise exceptions.PermissionError: user can't access this file
189 @raise exceptions.DataError: path is invalid 189 @raise exceptions.DataError: path is invalid
190 @raise NotFound: path lead to a non existing file/directory 190 @raise NotFound: path lead to a non existing file/directory
191 """ 191 """
192 path_elts = filter(None, path.split(u"/")) 192 path_elts = [_f for _f in path.split("/") if _f]
193 193
194 if u".." in path_elts: 194 if ".." in path_elts:
195 log.warning(_( 195 log.warning(_(
196 u'parent dir ("..") found in path, hack attempt? path is {path} ' 196 'parent dir ("..") found in path, hack attempt? path is {path} '
197 u'[{profile}]').format(path=path, profile=client.profile)) 197 '[{profile}]').format(path=path, profile=client.profile))
198 raise exceptions.PermissionError(u"illegal path elements") 198 raise exceptions.PermissionError("illegal path elements")
199 199
200 if not path_elts: 200 if not path_elts:
201 raise exceptions.DataError(_(u"path is invalid: {path}").format(path=path)) 201 raise exceptions.DataError(_("path is invalid: {path}").format(path=path))
202 202
203 node = client._XEP_0329_root_node 203 node = client._XEP_0329_root_node
204 204
205 while path_elts: 205 while path_elts:
206 if node.type == TYPE_VIRTUAL: 206 if node.type == TYPE_VIRTUAL:
210 raise exceptions.NotFound 210 raise exceptions.NotFound
211 elif node.type == TYPE_PATH: 211 elif node.type == TYPE_PATH:
212 break 212 break
213 213
214 if not node.checkPermissions(client, peer_jid, perms=perms): 214 if not node.checkPermissions(client, peer_jid, perms=perms):
215 raise exceptions.PermissionError(u"permission denied") 215 raise exceptions.PermissionError("permission denied")
216 216
217 return node, u"/".join(path_elts) 217 return node, "/".join(path_elts)
218 218
219 def findByLocalPath(self, path): 219 def findByLocalPath(self, path):
220 """retrieve nodes linking to local path 220 """retrieve nodes linking to local path
221 221
222 @return (list[ShareNode]): found nodes associated to path 222 @return (list[ShareNode]): found nodes associated to path
228 except KeyError: 228 except KeyError:
229 raise exceptions.NotFound 229 raise exceptions.NotFound
230 230
231 def _getSharedPaths(self, node, paths): 231 def _getSharedPaths(self, node, paths):
232 if node.type == TYPE_VIRTUAL: 232 if node.type == TYPE_VIRTUAL:
233 for node in node.itervalues(): 233 for node in node.values():
234 self._getSharedPaths(node, paths) 234 self._getSharedPaths(node, paths)
235 elif node.type == TYPE_PATH: 235 elif node.type == TYPE_PATH:
236 paths.setdefault(node.path, []).append(node) 236 paths.setdefault(node.path, []).append(node)
237 else: 237 else:
238 raise exceptions.InternalError( 238 raise exceptions.InternalError(
239 u"unknown node type: {type}".format(type=node.type) 239 "unknown node type: {type}".format(type=node.type)
240 ) 240 )
241 241
242 def getSharedPaths(self): 242 def getSharedPaths(self):
243 """retrieve nodes by shared path 243 """retrieve nodes by shared path
244 244
265 "FISList", 265 "FISList",
266 ".plugin", 266 ".plugin",
267 in_sign="ssa{ss}s", 267 in_sign="ssa{ss}s",
268 out_sign="aa{ss}", 268 out_sign="aa{ss}",
269 method=self._listFiles, 269 method=self._listFiles,
270 async=True, 270 async_=True,
271 ) 271 )
272 host.bridge.addMethod( 272 host.bridge.addMethod(
273 "FISLocalSharesGet", 273 "FISLocalSharesGet",
274 ".plugin", 274 ".plugin",
275 in_sign="s", 275 in_sign="s",
318 """ 318 """
319 if client.is_component: 319 if client.is_component:
320 return True, None 320 return True, None
321 321
322 try: 322 try:
323 name = file_data[u"name"] 323 name = file_data["name"]
324 except KeyError: 324 except KeyError:
325 return True, None 325 return True, None
326 assert u"/" not in name 326 assert "/" not in name
327 327
328 path = file_data.get(u"path") 328 path = file_data.get("path")
329 if path is not None: 329 if path is not None:
330 # we have a path, we can follow it to find node 330 # we have a path, we can follow it to find node
331 try: 331 try:
332 node, rem_path = ShareNode.find(client, path, session[u"peer_jid"]) 332 node, rem_path = ShareNode.find(client, path, session["peer_jid"])
333 except (exceptions.PermissionError, exceptions.NotFound): 333 except (exceptions.PermissionError, exceptions.NotFound):
334 #  no file, or file not allowed, we continue normal workflow 334 #  no file, or file not allowed, we continue normal workflow
335 return True, None 335 return True, None
336 except exceptions.DataError: 336 except exceptions.DataError:
337 log.warning(_(u"invalid path: {path}").format(path=path)) 337 log.warning(_("invalid path: {path}").format(path=path))
338 return True, None 338 return True, None
339 339
340 if node.type == TYPE_VIRTUAL: 340 if node.type == TYPE_VIRTUAL:
341 # we have a virtual node, so name must link to a path node 341 # we have a virtual node, so name must link to a path node
342 try: 342 try:
346 elif node.type == TYPE_PATH: 346 elif node.type == TYPE_PATH:
347 # we have a path node, so we can retrieve the full path now 347 # we have a path node, so we can retrieve the full path now
348 path = os.path.join(node.path, rem_path, name) 348 path = os.path.join(node.path, rem_path, name)
349 else: 349 else:
350 raise exceptions.InternalError( 350 raise exceptions.InternalError(
351 u"unknown type: {type}".format(type=node.type) 351 "unknown type: {type}".format(type=node.type)
352 ) 352 )
353 if not os.path.exists(path): 353 if not os.path.exists(path):
354 return True, None 354 return True, None
355 size = os.path.getsize(path) 355 size = os.path.getsize(path)
356 else: 356 else:
358 try: 358 try:
359 name_data = client._XEP_0329_names_data[name] 359 name_data = client._XEP_0329_names_data[name]
360 except KeyError: 360 except KeyError:
361 return True, None 361 return True, None
362 362
363 for path, shared_file in name_data.iteritems(): 363 for path, shared_file in name_data.items():
364 if True: #  FIXME: filters are here 364 if True: #  FIXME: filters are here
365 break 365 break
366 else: 366 else:
367 return True, None 367 return True, None
368 parent_node = shared_file[u"parent"] 368 parent_node = shared_file["parent"]
369 if not parent_node.checkPermissions(client, session[u"peer_jid"]): 369 if not parent_node.checkPermissions(client, session["peer_jid"]):
370 log.warning( 370 log.warning(
371 _( 371 _(
372 u"{peer_jid} requested a file (s)he can't access [{profile}]" 372 "{peer_jid} requested a file (s)he can't access [{profile}]"
373 ).format(peer_jid=session[u"peer_jid"], profile=client.profile) 373 ).format(peer_jid=session["peer_jid"], profile=client.profile)
374 ) 374 )
375 return True, None 375 return True, None
376 size = shared_file[u"size"] 376 size = shared_file["size"]
377 377
378 file_data[u"size"] = size 378 file_data["size"] = size
379 file_elt.addElement(u"size", content=unicode(size)) 379 file_elt.addElement("size", content=str(size))
380 hash_algo = file_data[u"hash_algo"] = self._h.getDefaultAlgo() 380 hash_algo = file_data["hash_algo"] = self._h.getDefaultAlgo()
381 hasher = file_data[u"hash_hasher"] = self._h.getHasher(hash_algo) 381 hasher = file_data["hash_hasher"] = self._h.getHasher(hash_algo)
382 file_elt.addChild(self._h.buildHashUsedElt(hash_algo)) 382 file_elt.addChild(self._h.buildHashUsedElt(hash_algo))
383 content_data["stream_object"] = stream.FileStreamObject( 383 content_data["stream_object"] = stream.FileStreamObject(
384 self.host, 384 self.host,
385 client, 385 client,
386 path, 386 path,
399 d = defer.maybeDeferred(root_nodes_cb, client, iq_elt) 399 d = defer.maybeDeferred(root_nodes_cb, client, iq_elt)
400 else: 400 else:
401 d = defer.maybeDeferred(files_from_node_cb, client, iq_elt, node) 401 d = defer.maybeDeferred(files_from_node_cb, client, iq_elt, node)
402 d.addErrback( 402 d.addErrback(
403 lambda failure_: log.error( 403 lambda failure_: log.error(
404 _(u"error while retrieving files: {msg}").format(msg=failure_) 404 _("error while retrieving files: {msg}").format(msg=failure_)
405 ) 405 )
406 ) 406 )
407 407
408 def _iqError(self, client, iq_elt, condition="item-not-found"): 408 def _iqError(self, client, iq_elt, condition="item-not-found"):
409 error_elt = jabber_error.StanzaError(condition).toResponse(iq_elt) 409 error_elt = jabber_error.StanzaError(condition).toResponse(iq_elt)
450 for name in sorted(os.listdir(path.encode("utf-8")), key=lambda n: n.lower()): 450 for name in sorted(os.listdir(path.encode("utf-8")), key=lambda n: n.lower()):
451 try: 451 try:
452 name = name.decode("utf-8", "strict") 452 name = name.decode("utf-8", "strict")
453 except UnicodeDecodeError as e: 453 except UnicodeDecodeError as e:
454 log.warning( 454 log.warning(
455 _(u"ignoring invalid unicode name ({name}): {msg}").format( 455 _("ignoring invalid unicode name ({name}): {msg}").format(
456 name=name.decode("utf-8", "replace"), msg=e 456 name=name.decode("utf-8", "replace"), msg=e
457 ) 457 )
458 ) 458 )
459 continue 459 continue
460 full_path = os.path.join(path, name) 460 full_path = os.path.join(path, name)
461 self._addPathData(client, query_elt, full_path, node) 461 self._addPathData(client, query_elt, full_path, node)
462 462
463 def _virtualNodeHandler(self, client, peer_jid, iq_elt, query_elt, node): 463 def _virtualNodeHandler(self, client, peer_jid, iq_elt, query_elt, node):
464 """Fill query_elt for virtual nodes""" 464 """Fill query_elt for virtual nodes"""
465 for name, child_node in node.iteritems(): 465 for name, child_node in node.items():
466 if not child_node.checkPermissions(client, peer_jid, check_parents=False): 466 if not child_node.checkPermissions(client, peer_jid, check_parents=False):
467 continue 467 continue
468 node_type = child_node.type 468 node_type = child_node.type
469 if node_type == TYPE_VIRTUAL: 469 if node_type == TYPE_VIRTUAL:
470 directory_elt = query_elt.addElement("directory") 470 directory_elt = query_elt.addElement("directory")
471 directory_elt["name"] = name 471 directory_elt["name"] = name
472 elif node_type == TYPE_PATH: 472 elif node_type == TYPE_PATH:
473 self._addPathData(client, query_elt, child_node.path, child_node) 473 self._addPathData(client, query_elt, child_node.path, child_node)
474 else: 474 else:
475 raise exceptions.InternalError( 475 raise exceptions.InternalError(
476 _(u"unexpected type: {type}").format(type=node_type) 476 _("unexpected type: {type}").format(type=node_type)
477 ) 477 )
478 478
479 def _getRootNodesCb(self, client, iq_elt): 479 def _getRootNodesCb(self, client, iq_elt):
480 peer_jid = jid.JID(iq_elt["from"]) 480 peer_jid = jid.JID(iq_elt["from"])
481 iq_result_elt = xmlstream.toResponse(iq_elt, "result") 481 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
482 query_elt = iq_result_elt.addElement((NS_FIS, "query")) 482 query_elt = iq_result_elt.addElement((NS_FIS, "query"))
483 for name, node in client._XEP_0329_root_node.iteritems(): 483 for name, node in client._XEP_0329_root_node.items():
484 if not node.checkPermissions(client, peer_jid, check_parents=False): 484 if not node.checkPermissions(client, peer_jid, check_parents=False):
485 continue 485 continue
486 directory_elt = query_elt.addElement("directory") 486 directory_elt = query_elt.addElement("directory")
487 directory_elt["name"] = name 487 directory_elt["name"] = name
488 client.send(iq_result_elt) 488 client.send(iq_result_elt)
489 489
490 def _getFilesFromNodeCb(self, client, iq_elt, node_path): 490 def _getFilesFromNodeCb(self, client, iq_elt, node_path):
491 """Main method to retrieve files/directories from a node_path""" 491 """Main method to retrieve files/directories from a node_path"""
492 peer_jid = jid.JID(iq_elt[u"from"]) 492 peer_jid = jid.JID(iq_elt["from"])
493 try: 493 try:
494 node, path = ShareNode.find(client, node_path, peer_jid) 494 node, path = ShareNode.find(client, node_path, peer_jid)
495 except (exceptions.PermissionError, exceptions.NotFound): 495 except (exceptions.PermissionError, exceptions.NotFound):
496 return self._iqError(client, iq_elt) 496 return self._iqError(client, iq_elt)
497 except exceptions.DataError: 497 except exceptions.DataError:
499 499
500 node_type = node.type 500 node_type = node.type
501 peer_jid = jid.JID(iq_elt["from"]) 501 peer_jid = jid.JID(iq_elt["from"])
502 iq_result_elt = xmlstream.toResponse(iq_elt, "result") 502 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
503 query_elt = iq_result_elt.addElement((NS_FIS, "query")) 503 query_elt = iq_result_elt.addElement((NS_FIS, "query"))
504 query_elt[u"node"] = node_path 504 query_elt["node"] = node_path
505 505
506 # we now fill query_elt according to node_type 506 # we now fill query_elt according to node_type
507 if node_type == TYPE_PATH: 507 if node_type == TYPE_PATH:
508 #  it's a physical path 508 #  it's a physical path
509 self._pathNodeHandler(client, iq_elt, query_elt, node, path) 509 self._pathNodeHandler(client, iq_elt, query_elt, node, path)
510 elif node_type == TYPE_VIRTUAL: 510 elif node_type == TYPE_VIRTUAL:
511 assert not path 511 assert not path
512 self._virtualNodeHandler(client, peer_jid, iq_elt, query_elt, node) 512 self._virtualNodeHandler(client, peer_jid, iq_elt, query_elt, node)
513 else: 513 else:
514 raise exceptions.InternalError( 514 raise exceptions.InternalError(
515 _(u"unknown node type: {type}").format(type=node_type) 515 _("unknown node type: {type}").format(type=node_type)
516 ) 516 )
517 517
518 client.send(iq_result_elt) 518 client.send(iq_result_elt)
519 519
520 def onRequest(self, iq_elt, client): 520 def onRequest(self, iq_elt, client):
531 @return (tuple[jid.JID, jid.JID]): peer_jid and owner 531 @return (tuple[jid.JID, jid.JID]): peer_jid and owner
532 """ 532 """
533 to_jid = jid.JID(iq_elt['to']) 533 to_jid = jid.JID(iq_elt['to'])
534 if to_jid.user: 534 if to_jid.user:
535 user = self.host.plugins['XEP-0106'].unescape(to_jid.user) 535 user = self.host.plugins['XEP-0106'].unescape(to_jid.user)
536 if u'@' in user: 536 if '@' in user:
537 # a full jid is specified 537 # a full jid is specified
538 owner = jid.JID(user) 538 owner = jid.JID(user)
539 else: 539 else:
540 # only user part is specified, we use our own host to build the full jid 540 # only user part is specified, we use our own host to build the full jid
541 owner = jid.JID(None, (user, client.host, None)) 541 owner = jid.JID(None, (user, client.host, None))
549 def _compGetRootNodesCb(self, client, iq_elt): 549 def _compGetRootNodesCb(self, client, iq_elt):
550 peer_jid, owner = self._compParseJids(client, iq_elt) 550 peer_jid, owner = self._compParseJids(client, iq_elt)
551 files_data = yield self.host.memory.getFiles( 551 files_data = yield self.host.memory.getFiles(
552 client, 552 client,
553 peer_jid=peer_jid, 553 peer_jid=peer_jid,
554 parent=u"", 554 parent="",
555 type_=C.FILE_TYPE_DIRECTORY, 555 type_=C.FILE_TYPE_DIRECTORY,
556 owner=owner, 556 owner=owner,
557 ) 557 )
558 iq_result_elt = xmlstream.toResponse(iq_elt, "result") 558 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
559 query_elt = iq_result_elt.addElement((NS_FIS, "query")) 559 query_elt = iq_result_elt.addElement((NS_FIS, "query"))
560 for file_data in files_data: 560 for file_data in files_data:
561 name = file_data[u"name"] 561 name = file_data["name"]
562 directory_elt = query_elt.addElement(u"directory") 562 directory_elt = query_elt.addElement("directory")
563 directory_elt[u"name"] = name 563 directory_elt["name"] = name
564 client.send(iq_result_elt) 564 client.send(iq_result_elt)
565 565
566 @defer.inlineCallbacks 566 @defer.inlineCallbacks
567 def _compGetFilesFromNodeCb(self, client, iq_elt, node_path): 567 def _compGetFilesFromNodeCb(self, client, iq_elt, node_path):
568 """Retrieve files from local files repository according to permissions 568 """Retrieve files from local files repository according to permissions
582 return 582 return
583 except exceptions.PermissionError: 583 except exceptions.PermissionError:
584 self._iqError(client, iq_elt, condition='not-allowed') 584 self._iqError(client, iq_elt, condition='not-allowed')
585 return 585 return
586 except Exception as e: 586 except Exception as e:
587 log.error(u"internal server error: {e}".format(e=e)) 587 log.error("internal server error: {e}".format(e=e))
588 self._iqError(client, iq_elt, condition='internal-server-error') 588 self._iqError(client, iq_elt, condition='internal-server-error')
589 return 589 return
590 iq_result_elt = xmlstream.toResponse(iq_elt, "result") 590 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
591 query_elt = iq_result_elt.addElement((NS_FIS, "query")) 591 query_elt = iq_result_elt.addElement((NS_FIS, "query"))
592 query_elt[u"node"] = node_path 592 query_elt["node"] = node_path
593 if not self.host.trigger.point( 593 if not self.host.trigger.point(
594 u"XEP-0329_compGetFilesFromNode", client, iq_elt, owner, node_path, files_data 594 "XEP-0329_compGetFilesFromNode", client, iq_elt, owner, node_path, files_data
595 ): 595 ):
596 return 596 return
597 for file_data in files_data: 597 for file_data in files_data:
598 file_elt = self._jf.buildFileElementFromDict( 598 file_elt = self._jf.buildFileElementFromDict(
599 file_data, modified=file_data.get(u"modified", file_data[u"created"]) 599 file_data, modified=file_data.get("modified", file_data["created"])
600 ) 600 )
601 query_elt.addChild(file_elt) 601 query_elt.addChild(file_elt)
602 client.send(iq_result_elt) 602 client.send(iq_result_elt)
603 603
604 def onComponentRequest(self, iq_elt, client): 604 def onComponentRequest(self, iq_elt, client):
615 # we have a file 615 # we have a file
616 try: 616 try:
617 file_data = self._jf.parseFileElement(elt) 617 file_data = self._jf.parseFileElement(elt)
618 except exceptions.DataError: 618 except exceptions.DataError:
619 continue 619 continue
620 file_data[u"type"] = C.FILE_TYPE_FILE 620 file_data["type"] = C.FILE_TYPE_FILE
621 elif elt.name == "directory" and elt.uri == NS_FIS: 621 elif elt.name == "directory" and elt.uri == NS_FIS:
622 # we have a directory 622 # we have a directory
623 623
624 file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY} 624 file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY}
625 else: 625 else:
626 log.warning( 626 log.warning(
627 _(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml()) 627 _("unexpected element, ignoring: {elt}").format(elt=elt.toXml())
628 ) 628 )
629 continue 629 continue
630 files.append(file_data) 630 files.append(file_data)
631 return files 631 return files
632 632
633 # file methods # 633 # file methods #
634 634
635 def _serializeData(self, files_data): 635 def _serializeData(self, files_data):
636 for file_data in files_data: 636 for file_data in files_data:
637 for key, value in file_data.iteritems(): 637 for key, value in file_data.items():
638 file_data[key] = ( 638 file_data[key] = (
639 json.dumps(value) if key in ("extra",) else unicode(value) 639 json.dumps(value) if key in ("extra",) else str(value)
640 ) 640 )
641 return files_data 641 return files_data
642 642
643 def _listFiles(self, target_jid, path, extra, profile): 643 def _listFiles(self, target_jid, path, extra, profile):
644 client = self.host.getClient(profile) 644 client = self.host.getClient(profile)
668 def _localSharesGet(self, profile): 668 def _localSharesGet(self, profile):
669 client = self.host.getClient(profile) 669 client = self.host.getClient(profile)
670 return self.localSharesGet(client) 670 return self.localSharesGet(client)
671 671
672 def localSharesGet(self, client): 672 def localSharesGet(self, client):
673 return client._XEP_0329_root_node.getSharedPaths().keys() 673 return list(client._XEP_0329_root_node.getSharedPaths().keys())
674 674
675 def _sharePath(self, name, path, access, profile): 675 def _sharePath(self, name, path, access, profile):
676 client = self.host.getClient(profile) 676 client = self.host.getClient(profile)
677 access = json.loads(access) 677 access = json.loads(access)
678 return self.sharePath(client, name or None, path, access) 678 return self.sharePath(client, name or None, path, access)
679 679
680 def sharePath(self, client, name, path, access): 680 def sharePath(self, client, name, path, access):
681 if client.is_component: 681 if client.is_component:
682 raise exceptions.ClientTypeError 682 raise exceptions.ClientTypeError
683 if not os.path.exists(path): 683 if not os.path.exists(path):
684 raise ValueError(_(u"This path doesn't exist!")) 684 raise ValueError(_("This path doesn't exist!"))
685 if not path or not path.strip(u" /"): 685 if not path or not path.strip(" /"):
686 raise ValueError(_(u"A path need to be specified")) 686 raise ValueError(_("A path need to be specified"))
687 if not isinstance(access, dict): 687 if not isinstance(access, dict):
688 raise ValueError(_(u"access must be a dict")) 688 raise ValueError(_("access must be a dict"))
689 689
690 node = client._XEP_0329_root_node 690 node = client._XEP_0329_root_node
691 node_type = TYPE_PATH 691 node_type = TYPE_PATH
692 if os.path.isfile(path): 692 if os.path.isfile(path):
693 # we have a single file, the workflow is diferrent as we store all single 693 # we have a single file, the workflow is diferrent as we store all single
694 # files in the same dir 694 # files in the same dir
695 node = node.getOrCreate(SINGLE_FILES_DIR) 695 node = node.getOrCreate(SINGLE_FILES_DIR)
696 696
697 if not name: 697 if not name:
698 name = os.path.basename(path.rstrip(u" /")) 698 name = os.path.basename(path.rstrip(" /"))
699 if not name: 699 if not name:
700 raise exceptions.InternalError(_(u"Can't find a proper name")) 700 raise exceptions.InternalError(_("Can't find a proper name"))
701 701
702 if name in node or name == SINGLE_FILES_DIR: 702 if name in node or name == SINGLE_FILES_DIR:
703 idx = 1 703 idx = 1
704 new_name = name + "_" + unicode(idx) 704 new_name = name + "_" + str(idx)
705 while new_name in node: 705 while new_name in node:
706 idx += 1 706 idx += 1
707 new_name = name + "_" + unicode(idx) 707 new_name = name + "_" + str(idx)
708 name = new_name 708 name = new_name
709 log.info(_( 709 log.info(_(
710 u"A directory with this name is already shared, renamed to {new_name} " 710 "A directory with this name is already shared, renamed to {new_name} "
711 u"[{profile}]".format( new_name=new_name, profile=client.profile))) 711 "[{profile}]".format( new_name=new_name, profile=client.profile)))
712 712
713 ShareNode(name=name, parent=node, type_=node_type, access=access, path=path) 713 ShareNode(name=name, parent=node, type_=node_type, access=access, path=path)
714 self.host.bridge.FISSharedPathNew(path, name, client.profile) 714 self.host.bridge.FISSharedPathNew(path, name, client.profile)
715 return name 715 return name
716 716
723 for node in nodes: 723 for node in nodes:
724 node.removeFromParent() 724 node.removeFromParent()
725 self.host.bridge.FISSharedPathRemoved(path, client.profile) 725 self.host.bridge.FISSharedPathRemoved(path, client.profile)
726 726
727 727
728 @implementer(iwokkel.IDisco)
728 class XEP_0329_handler(xmlstream.XMPPHandler): 729 class XEP_0329_handler(xmlstream.XMPPHandler):
729 implements(iwokkel.IDisco)
730 730
731 def __init__(self, plugin_parent): 731 def __init__(self, plugin_parent):
732 self.plugin_parent = plugin_parent 732 self.plugin_parent = plugin_parent
733 self.host = plugin_parent.host 733 self.host = plugin_parent.host
734 734