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
This commit is contained in:
Goffi 2019-08-13 19:08:41 +02:00
commit fbfe96c505

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SAT: a jabber client
@ -26,7 +26,7 @@ log = getLogger(__name__)
import os.path
import copy
from collections import namedtuple
from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
from configparser import SafeConfigParser, NoOptionError, NoSectionError
from uuid import uuid4
from twisted.python import failure
from twisted.internet import defer, reactor, error
@ -76,7 +76,7 @@ class Sessions(object):
session_id = str(uuid4())
elif session_id in self._sessions:
raise exceptions.ConflictError(
u"Session id {} is already used".format(session_id)
"Session id {} is already used".format(session_id)
)
timer = reactor.callLater(self.timeout, self._purgeSession, session_id)
if session_data is None:
@ -99,9 +99,9 @@ class Sessions(object):
pass
del self._sessions[session_id]
log.debug(
u"Session {} purged{}".format(
"Session {} purged{}".format(
session_id,
u" (profile {})".format(profile) if profile is not None else u"",
" (profile {})".format(profile) if profile is not None else "",
)
)
@ -147,10 +147,10 @@ class Sessions(object):
self._purgeSession(session_id)
def keys(self):
return self._sessions.keys()
return list(self._sessions.keys())
def iterkeys(self):
return self._sessions.iterkeys()
return iter(self._sessions.keys())
class ProfileSessions(Sessions):
@ -165,7 +165,7 @@ class ProfileSessions(Sessions):
@return: a list containing the sessions ids
"""
ret = []
for session_id in self._sessions.iterkeys():
for session_id in self._sessions.keys():
try:
timer, session_data, profile_set = self._sessions[session_id]
except ValueError:
@ -245,7 +245,7 @@ def fixLocalDir(silent=True):
if not silent:
log.warning(
_(
u"A database has been found in the default local_dir for previous versions (< 0.5)"
"A database has been found in the default local_dir for previous versions (< 0.5)"
)
)
tools_config.fixConfigOption("", "local_dir", old_default, silent)
@ -306,10 +306,10 @@ class Memory(object):
if os.path.exists(filename):
try:
self.params.load_xml(filename)
log.debug(_(u"Parameters loaded from file: %s") % filename)
log.debug(_("Parameters loaded from file: %s") % filename)
return True
except Exception as e:
log.error(_(u"Can't load parameters from file: %s") % e)
log.error(_("Can't load parameters from file: %s") % e)
return False
def save_xml(self, filename):
@ -324,10 +324,10 @@ class Memory(object):
filename = os.path.expanduser(filename)
try:
self.params.save_xml(filename)
log.debug(_(u"Parameters saved to file: %s") % filename)
log.debug(_("Parameters saved to file: %s") % filename)
return True
except Exception as e:
log.error(_(u"Can't save parameters to file: %s") % e)
log.error(_("Can't save parameters to file: %s") % e)
return False
def load(self):
@ -356,7 +356,7 @@ class Memory(object):
def createSession(__):
"""Called once params are loaded."""
self._entities_cache[profile] = {}
log.info(u"[{}] Profile session started".format(profile))
log.info("[{}] Profile session started".format(profile))
return False
def backendInitialised(__):
@ -392,13 +392,13 @@ class Memory(object):
@param profile: %(doc_profile)s
"""
if self.host.isConnected(profile):
log.debug(u"Disconnecting profile because of session stop")
log.debug("Disconnecting profile because of session stop")
self.host.disconnect(profile)
self.auth_sessions.profileDelUnique(profile)
try:
self._entities_cache[profile]
except KeyError:
log.warning(u"Profile was not in cache")
log.warning("Profile was not in cache")
def _isSessionStarted(self, profile_key):
return self.isSessionStarted(self.getProfileName(profile_key))
@ -428,10 +428,10 @@ class Memory(object):
def check_result(result):
if not result:
log.warning(u"Authentication failure of profile {}".format(profile))
log.warning("Authentication failure of profile {}".format(profile))
raise failure.Failure(
exceptions.PasswordError(
u"The provided profile password doesn't match."
"The provided profile password doesn't match."
)
)
if (
@ -460,7 +460,7 @@ class Memory(object):
self.auth_sessions.newSession(
{C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile
)
log.debug(u"auth session created for profile %s" % profile)
log.debug("auth session created for profile %s" % profile)
d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY]))
@ -476,7 +476,7 @@ class Memory(object):
except KeyError:
log.error(
_(
u"Trying to purge roster status cache for a profile not in memory: [%s]"
"Trying to purge roster status cache for a profile not in memory: [%s]"
)
% profile
)
@ -489,7 +489,7 @@ class Memory(object):
@return (list[unicode]): selected profiles
"""
if not clients and not components:
log.warning(_(u"requesting no profiles at all"))
log.warning(_("requesting no profiles at all"))
return []
profiles = self.storage.getProfilesList()
if clients and components:
@ -533,20 +533,20 @@ class Memory(object):
@raise exceptions.NotFound: component is not a known plugin import name
"""
if not name:
raise ValueError(u"Empty profile name")
raise ValueError("Empty profile name")
if name[0] == "@":
raise ValueError(u"A profile name can't start with a '@'")
raise ValueError("A profile name can't start with a '@'")
if "\n" in name:
raise ValueError(u"A profile name can't contain line feed ('\\n')")
raise ValueError("A profile name can't contain line feed ('\\n')")
if name in self._entities_cache:
raise exceptions.ConflictError(u"A session for this profile exists")
raise exceptions.ConflictError("A session for this profile exists")
if component:
if not component in self.host.plugins:
raise exceptions.NotFound(
_(
u"Can't find component {component} entry point".format(
"Can't find component {component} entry point".format(
component=component
)
)
@ -664,7 +664,7 @@ class Memory(object):
def _getPresenceStatuses(self, profile_key):
ret = self.getPresenceStatuses(profile_key)
return {entity.full(): data for entity, data in ret.iteritems()}
return {entity.full(): data for entity, data in ret.items()}
def getPresenceStatuses(self, profile_key):
"""Get all the presence statuses of a profile
@ -676,8 +676,8 @@ class Memory(object):
profile_cache = self._getProfileCache(client)
entities_presence = {}
for entity_jid, entity_data in profile_cache.iteritems():
for resource, resource_data in entity_data.iteritems():
for entity_jid, entity_data in profile_cache.items():
for resource, resource_data in entity_data.items():
full_jid = copy.copy(entity_jid)
full_jid.resource = resource
try:
@ -736,7 +736,7 @@ class Memory(object):
entity_data = profile_cache[entity_jid.userhostJID()]
except KeyError:
raise exceptions.UnknownEntityError(
u"Entity {} not in cache".format(entity_jid)
"Entity {} not in cache".format(entity_jid)
)
resources = set(entity_data.keys())
resources.discard(None)
@ -758,7 +758,7 @@ class Memory(object):
try:
presence_data = self.getEntityDatum(full_jid, "presence", client.profile)
except KeyError:
log.debug(u"Can't get presence data for {}".format(full_jid))
log.debug("Can't get presence data for {}".format(full_jid))
else:
if presence_data.show != C.PRESENCE_UNAVAILABLE:
available.append(resource)
@ -787,7 +787,7 @@ class Memory(object):
try:
resources = self.getAllResources(client, entity_jid)
except exceptions.UnknownEntityError:
log.warning(u"Entity is not in cache, we can't find any resource")
log.warning("Entity is not in cache, we can't find any resource")
return None
priority_resources = []
for resource in resources:
@ -796,13 +796,13 @@ class Memory(object):
try:
presence_data = self.getEntityDatum(full_jid, "presence", client.profile)
except KeyError:
log.debug(u"No presence information for {}".format(full_jid))
log.debug("No presence information for {}".format(full_jid))
continue
priority_resources.append((resource, presence_data.priority))
try:
return max(priority_resources, key=lambda res_tuple: res_tuple[1])[0]
except ValueError:
log.warning(u"No resource found at all for {}".format(entity_jid))
log.warning("No resource found at all for {}".format(entity_jid))
return None
## Entities data ##
@ -835,8 +835,8 @@ class Memory(object):
"""
profile_cache = self._getProfileCache(client)
# we construct a list of all known full jids (bare jid of entities x resources)
for bare_jid, entity_data in profile_cache.iteritems():
for resource in entity_data.iterkeys():
for bare_jid, entity_data in profile_cache.items():
for resource in entity_data.keys():
if resource is None:
continue
full_jid = copy.copy(bare_jid)
@ -871,9 +871,9 @@ class Memory(object):
entity_data[key] = value
if key in self._key_signals and not silent:
if not isinstance(value, basestring):
if not isinstance(value, str):
log.error(
u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(
"Setting a non string value ({}) for a key ({}) which has a signal flag".format(
value, key
)
)
@ -905,7 +905,7 @@ class Memory(object):
entity_data = profile_cache[jid_.userhostJID()][jid_.resource]
except KeyError:
raise exceptions.UnknownEntityError(
u"Entity {} not in cache".format(jid_)
"Entity {} not in cache".format(jid_)
)
try:
del entity_data[key]
@ -919,7 +919,7 @@ class Memory(object):
ret = self.getEntitiesData(
[jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key
)
return {jid_.full(): data for jid_, data in ret.iteritems()}
return {jid_.full(): data for jid_, data in ret.items()}
def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE):
"""Get a list of cached values for several entities at once
@ -961,8 +961,8 @@ class Memory(object):
continue
ret_data[entity.full()] = fillEntityData(entity_cache_data, keys_list)
else:
for bare_jid, data in profile_cache.iteritems():
for resource, entity_cache_data in data.iteritems():
for bare_jid, data in profile_cache.items():
for resource, entity_cache_data in data.items():
full_jid = copy.copy(bare_jid)
full_jid.resource = resource
ret_data[full_jid] = fillEntityData(entity_cache_data)
@ -987,7 +987,7 @@ class Memory(object):
entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource]
except KeyError:
raise exceptions.UnknownEntityError(
u"Entity {} not in cache (was requesting {})".format(
"Entity {} not in cache (was requesting {})".format(
entity_jid, keys_list
)
)
@ -1030,14 +1030,14 @@ class Memory(object):
del profile_cache[entity_jid]
except KeyError:
raise exceptions.UnknownEntityError(
u"Entity {} not in cache".format(entity_jid)
"Entity {} not in cache".format(entity_jid)
)
else:
try:
del profile_cache[entity_jid.userhostJID()][entity_jid.resource]
except KeyError:
raise exceptions.UnknownEntityError(
u"Entity {} not in cache".format(entity_jid)
"Entity {} not in cache".format(entity_jid)
)
## Encryption ##
@ -1103,7 +1103,7 @@ class Memory(object):
def done(__):
log.debug(
_(u"Personal data (%(ns)s, %(key)s) has been successfuly encrypted")
_("Personal data (%(ns)s, %(key)s) has been successfuly encrypted")
% {"ns": C.MEMORY_CRYPTO_NAMESPACE, "key": data_key}
)
@ -1225,21 +1225,21 @@ class Memory(object):
# the owner has all rights
return
if not C.ACCESS_PERMS.issuperset(perms_to_check):
raise exceptions.InternalError(_(u"invalid permission"))
raise exceptions.InternalError(_("invalid permission"))
for perm in perms_to_check:
# we check each perm and raise PermissionError as soon as one condition is not valid
# we must never return here, we only return after the loop if nothing was blocking the access
try:
perm_data = file_data[u"access"][perm]
perm_type = perm_data[u"type"]
perm_data = file_data["access"][perm]
perm_type = perm_data["type"]
except KeyError:
raise exceptions.PermissionError()
if perm_type == C.ACCESS_TYPE_PUBLIC:
continue
elif perm_type == C.ACCESS_TYPE_WHITELIST:
try:
jids = perm_data[u"jids"]
jids = perm_data["jids"]
except KeyError:
raise exceptions.PermissionError()
if peer_jid.full() in jids:
@ -1248,7 +1248,7 @@ class Memory(object):
raise exceptions.PermissionError()
else:
raise exceptions.InternalError(
_(u"unknown access type: {type}").format(type=perm_type)
_("unknown access type: {type}").format(type=perm_type)
)
@defer.inlineCallbacks
@ -1257,7 +1257,7 @@ class Memory(object):
current = file_data
while True:
self.checkFilePermission(current, peer_jid, perms_to_check)
parent = current[u"parent"]
parent = current["parent"]
if not parent:
break
files_data = yield self.getFile(
@ -1266,7 +1266,7 @@ class Memory(object):
try:
current = files_data[0]
except IndexError:
raise exceptions.DataError(u"Missing parent")
raise exceptions.DataError("Missing parent")
@defer.inlineCallbacks
def _getParentDir(
@ -1283,15 +1283,15 @@ class Memory(object):
# if path is set, we have to retrieve parent directory of the file(s) from it
if parent is not None:
raise exceptions.ConflictError(
_(u"You can't use path and parent at the same time")
_("You can't use path and parent at the same time")
)
path_elts = filter(None, path.split(u"/"))
if {u"..", u"."}.intersection(path_elts):
raise ValueError(_(u'".." or "." can\'t be used in path'))
path_elts = [_f for _f in path.split("/") if _f]
if {"..", "."}.intersection(path_elts):
raise ValueError(_('".." or "." can\'t be used in path'))
# we retrieve all directories from path until we get the parent container
# non existing directories will be created
parent = u""
parent = ""
for idx, path_elt in enumerate(path_elts):
directories = yield self.storage.getFiles(
client,
@ -1306,12 +1306,12 @@ class Memory(object):
# from this point, directories don't exist anymore, we have to create them
elif len(directories) > 1:
raise exceptions.InternalError(
_(u"Several directories found, this should not happen")
_("Several directories found, this should not happen")
)
else:
directory = directories[0]
self.checkFilePermission(directory, peer_jid, perms_to_check)
parent = directory[u"id"]
parent = directory["id"]
defer.returnValue((parent, []))
@defer.inlineCallbacks
@ -1357,8 +1357,8 @@ class Memory(object):
"""
if peer_jid is None and perms_to_check or perms_to_check is None and peer_jid:
raise exceptions.InternalError(
u"if you want to disable permission check, both peer_jid and "
u"perms_to_check must be None"
"if you want to disable permission check, both peer_jid and "
"perms_to_check must be None"
)
if owner is not None:
owner = owner.userhostJID()
@ -1378,7 +1378,7 @@ class Memory(object):
try:
parent_data = parent_data[0]
except IndexError:
raise exceptions.DataError(u"mising parent")
raise exceptions.DataError("mising parent")
yield self.checkPermissionToRoot(
client, parent_data, peer_jid, perms_to_check
)
@ -1414,7 +1414,7 @@ class Memory(object):
@defer.inlineCallbacks
def setFile(
self, client, name, file_id=None, version=u"", parent=None, path=None,
self, client, name, file_id=None, version="", parent=None, path=None,
type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None,
namespace=None, mime_type=None, created=None, modified=None, owner=None,
access=None, extra=None, peer_jid=None, perms_to_check=(C.ACCESS_PERM_WRITE,)
@ -1481,7 +1481,7 @@ class Memory(object):
if type_ == C.FILE_TYPE_DIRECTORY:
if any(version, file_hash, size, mime_type):
raise ValueError(
u"version, file_hash, size and mime_type can't be set for a directory"
"version, file_hash, size and mime_type can't be set for a directory"
)
if owner is not None:
owner = owner.userhostJID()
@ -1498,7 +1498,7 @@ class Memory(object):
client,
name=new_dir,
file_id=new_dir_id,
version=u"",
version="",
parent=parent,
type_=C.FILE_TYPE_DIRECTORY,
namespace=namespace,
@ -1509,7 +1509,7 @@ class Memory(object):
)
parent = new_dir_id
elif parent is None:
parent = u""
parent = ""
yield self.storage.setFile(
client,
@ -1552,35 +1552,35 @@ class Memory(object):
@param files_path(unicode): path of the directory containing the actual files
@param file_data(dict): data of the file to delete
"""
if file_data[u'owner'] != peer_jid:
if file_data['owner'] != peer_jid:
raise exceptions.PermissionError(
u"file {file_name} can't be deleted, {peer_jid} is not the owner"
.format(file_name=file_data[u'name'], peer_jid=peer_jid.full()))
if file_data[u'type'] == C.FILE_TYPE_DIRECTORY:
sub_files = yield self.getFiles(client, peer_jid, parent=file_data[u'id'])
"file {file_name} can't be deleted, {peer_jid} is not the owner"
.format(file_name=file_data['name'], peer_jid=peer_jid.full()))
if file_data['type'] == C.FILE_TYPE_DIRECTORY:
sub_files = yield self.getFiles(client, peer_jid, parent=file_data['id'])
if sub_files and not recursive:
raise exceptions.DataError(_(u"Can't delete directory, it is not empty"))
raise exceptions.DataError(_("Can't delete directory, it is not empty"))
# we first delete the sub-files
for sub_file_data in sub_files:
yield self._deleteFile(client, peer_jid, recursive, sub_file_data)
# then the directory itself
yield self.storage.fileDelete(file_data[u'id'])
elif file_data[u'type'] == C.FILE_TYPE_FILE:
log.info(_(u"deleting file {name} with hash {file_hash}").format(
name=file_data[u'name'], file_hash=file_data[u'file_hash']))
yield self.storage.fileDelete(file_data[u'id'])
yield self.storage.fileDelete(file_data['id'])
elif file_data['type'] == C.FILE_TYPE_FILE:
log.info(_("deleting file {name} with hash {file_hash}").format(
name=file_data['name'], file_hash=file_data['file_hash']))
yield self.storage.fileDelete(file_data['id'])
references = yield self.getFiles(
client, peer_jid, file_hash=file_data[u'file_hash'])
client, peer_jid, file_hash=file_data['file_hash'])
if references:
log.debug(u"there are still references to the file, we keep it")
log.debug("there are still references to the file, we keep it")
else:
file_path = os.path.join(files_path, file_data[u'file_hash'])
log.info(_(u"no reference left to {file_path}, deleting").format(
file_path = os.path.join(files_path, file_data['file_hash'])
log.info(_("no reference left to {file_path}, deleting").format(
file_path=file_path))
os.unlink(file_path)
else:
raise exceptions.InternalError(u'Unexpected file type: {file_type}'
.format(file_type=file_data[u'type']))
raise exceptions.InternalError('Unexpected file type: {file_type}'
.format(file_type=file_data['type']))
@defer.inlineCallbacks
def fileDelete(self, client, peer_jid, file_id, recursive=False):
@ -1595,11 +1595,11 @@ class Memory(object):
# should be checked too
files_data = yield self.getFiles(client, peer_jid, file_id)
if not files_data:
raise exceptions.NotFound(u"Can't find the file with id {file_id}".format(
raise exceptions.NotFound("Can't find the file with id {file_id}".format(
file_id=file_id))
file_data = files_data[0]
if file_data[u"type"] != C.FILE_TYPE_DIRECTORY and recursive:
raise ValueError(u"recursive can only be set for directories")
if file_data["type"] != C.FILE_TYPE_DIRECTORY and recursive:
raise ValueError("recursive can only be set for directories")
files_path = self.host.getLocalPath(None, C.FILES_DIR, profile=False)
yield self._deleteFile(client, peer_jid, recursive, files_path, file_data)
@ -1618,6 +1618,6 @@ class Memory(object):
try:
presence_data = self.getEntityDatum(entity_jid, "presence", client.profile)
except KeyError:
log.debug(u"No presence information for {}".format(entity_jid))
log.debug("No presence information for {}".format(entity_jid))
return False
return presence_data.show != C.PRESENCE_UNAVAILABLE