diff libervia/server/server.py @ 1216:b2d067339de3

python 3 port: /!\ Python 3.6+ is now needed to use libervia /!\ instability may occur and features may not be working anymore, this will improve with time /!\ TxJSONRPC dependency has been removed The same procedure as in backend has been applied (check backend commit ab2696e34d29 logs for details). Removed now deprecated code (Pyjamas compiled browser part, legacy blog, JSON RPC related code). Adapted code to work without `html` and `themes` dirs.
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:12:31 +0200
parents a2df53dfbf46
children fe9782391f63
line wrap: on
line diff
--- a/libervia/server/server.py	Tue Aug 13 09:39:33 2019 +0200
+++ b/libervia/server/server.py	Tue Aug 13 19:12:31 2019 +0200
@@ -18,14 +18,10 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-import glob
 import os.path
 import sys
-import tempfile
-import shutil
-import uuid
-import urlparse
-import urllib
+import urllib.parse
+import urllib.request, urllib.error
 import time
 import copy
 from twisted.application import service
@@ -34,16 +30,12 @@
 from twisted.web import static
 from twisted.web import resource as web_resource
 from twisted.web import util as web_util
-from twisted.web import http
 from twisted.web import vhost
 from twisted.python.components import registerAdapter
 from twisted.python import failure
 from twisted.python import filepath
 from twisted.words.protocols.jabber import jid
 
-from txjsonrpc.web import jsonrpc
-from txjsonrpc import jsonrpclib
-
 from sat.core.log import getLogger
 
 from sat_frontends.bridge.dbus_bridge import (
@@ -58,7 +50,6 @@
 from sat.tools.common import regex
 from sat.tools.common import template
 from sat.tools.common import uri as common_uri
-from httplib import HTTPS_PORT
 import libervia
 from libervia.server import websockets
 from libervia.server.pages import LiberviaPage
@@ -73,7 +64,6 @@
     ssl = None
 
 from libervia.server.constants import Const as C
-from libervia.server.blog import MicroBlog
 from libervia.server import session_iface
 
 log = getLogger(__name__)
@@ -81,7 +71,7 @@
 
 # following value are set from twisted.plugins.libervia_server initialise
 # (see the comment there)
-DATA_DIR_DEFAULT = OPT_PARAMETERS_BOTH = OPT_PARAMETERS_CFG = coerceDataDir = None
+DATA_DIR_DEFAULT = OPT_PARAMETERS_BOTH = OPT_PARAMETERS_CFG = None
 DEFAULT_MASK = (inotify.IN_CREATE | inotify.IN_MODIFY | inotify.IN_MOVE_SELF
                 | inotify.IN_MOVED_TO)
 
@@ -102,7 +92,7 @@
 
     def watchDir(self, dir_path, callback, mask=DEFAULT_MASK, auto_add=False,
                  recursive=False, **kwargs):
-        log.info(_(u"Watching directory {dir_path}").format(dir_path=dir_path))
+        log.info(_("Watching directory {dir_path}").format(dir_path=dir_path))
         callbacks = [lambda __, filepath, mask: callback(self.host, filepath,
                      inotify.humanReadableMask(mask), **kwargs)]
         self.notifier.watch(
@@ -120,7 +110,7 @@
     def lock(self):
         """Prevent session from expiring"""
         self.__lock = True
-        self._expireCall.reset(sys.maxint)
+        self._expireCall.reset(sys.maxsize)
 
     def unlock(self):
         """Allow session to expire again, and touch it"""
@@ -147,6 +137,15 @@
         return web_resource.NoResource()
 
 
+    def getChild(self, path, request):
+        return super().getChild(path, request)
+
+    def getChildWithDefault(self, path, request):
+        return super().getChildWithDefault(path, request)
+
+    def getChildForRequest(self, request):
+        return super().getChildForRequest(request)
+
 class LiberviaRootResource(ProtectedFile):
     """Specialized resource for Libervia root
 
@@ -165,15 +164,12 @@
         self.cached_urls = {}
         self.main_menu = None
 
-    def __unicode__(self):
-        return (u"Root resource for {host_name} using {site_name} at {site_path} and "
-                u"deserving files at {path}".format(
+    def __str__(self):
+        return ("Root resource for {host_name} using {site_name} at {site_path} and "
+                "deserving files at {path}".format(
                 host_name=self.host_name, site_name=self.site_name,
                 site_path=self.site_path, path=self.path))
 
-    def __str__(self):
-        return self.__unicode__.encode('utf-8')
-
     def _initRedirections(self, options):
         url_redirections = options["url_redirections_dict"]
 
@@ -183,16 +179,16 @@
         self.redirections = {}
         self.inv_redirections = {}  # new URL to old URL map
 
-        for old, new_data in url_redirections.iteritems():
+        for old, new_data in url_redirections.items():
             # new_data can be a dictionary or a unicode url
             if isinstance(new_data, dict):
                 # new_data dict must contain either "url", "page" or "path" key
                 # (exclusive)
                 # if "path" is used, a file url is constructed with it
-                if len({"path", "url", "page"}.intersection(new_data.keys())) != 1:
+                if len({"path", "url", "page"}.intersection(list(new_data.keys()))) != 1:
                     raise ValueError(
-                        u'You must have one and only one of "url", "page" or "path" key '
-                        u'in your url_redirections_dict data')
+                        'You must have one and only one of "url", "page" or "path" key '
+                        'in your url_redirections_dict data')
                 if "url" in new_data:
                     new = new_data["url"]
                 elif "page" in new_data:
@@ -201,15 +197,15 @@
                     new.setdefault("path_args", [])
                     if not isinstance(new["path_args"], list):
                         log.error(
-                            _(u'"path_args" in redirection of {old} must be a list. '
-                              u'Ignoring the redirection'.format(old=old)))
+                            _('"path_args" in redirection of {old} must be a list. '
+                              'Ignoring the redirection'.format(old=old)))
                         continue
                     new.setdefault("query_args", {})
                     if not isinstance(new["query_args"], dict):
                         log.error(
                             _(
-                                u'"query_args" in redirection of {old} must be a '
-                                u'dictionary. Ignoring the redirection'.format(old=old)))
+                                '"query_args" in redirection of {old} must be a '
+                                'dictionary. Ignoring the redirection'.format(old=old)))
                         continue
                     new["path_args"] = [quote(a) for a in new["path_args"]]
                     # we keep an inversed dict of page redirection
@@ -223,17 +219,17 @@
 
                     # we need lists in query_args because it will be used
                     # as it in request.path_args
-                    for k, v in new["query_args"].iteritems():
-                        if isinstance(v, basestring):
+                    for k, v in new["query_args"].items():
+                        if isinstance(v, str):
                             new["query_args"][k] = [v]
                 elif "path" in new_data:
-                    new = "file:{}".format(urllib.quote(new_data["path"]))
-            elif isinstance(new_data, basestring):
+                    new = "file:{}".format(urllib.parse.quote(new_data["path"]))
+            elif isinstance(new_data, str):
                 new = new_data
                 new_data = {}
             else:
                 log.error(
-                    _(u"ignoring invalid redirection value: {new_data}").format(
+                    _("ignoring invalid redirection value: {new_data}").format(
                         new_data=new_data
                     )
                 )
@@ -244,7 +240,7 @@
                 # root URL special case
                 old = ""
             elif not old.startswith("/"):
-                log.error(_(u"redirected url must start with '/', got {value}. Ignoring")
+                log.error(_("redirected url must start with '/', got {value}. Ignoring")
                           .format(value=old))
                 continue
             else:
@@ -255,29 +251,29 @@
                 # which ared use dynamically when the request is done
                 self.redirections[old] = new
                 if not old:
-                    if new[u"type"] == u"page":
+                    if new["type"] == "page":
                         log.info(
-                            _(u"Root URL redirected to page {name}").format(
-                                name=new[u"page"]
+                            _("Root URL redirected to page {name}").format(
+                                name=new["page"]
                             )
                         )
                 else:
-                    if new[u"type"] == u"page":
-                        page = self.getPageByName(new[u"page"])
-                        url = page.getURL(*new.get(u"path_args", []))
+                    if new["type"] == "page":
+                        page = self.getPageByName(new["page"])
+                        url = page.getURL(*new.get("path_args", []))
                         self.inv_redirections[url] = old
                 continue
 
             # at this point we have a redirection URL in new, we can parse it
-            new_url = urlparse.urlsplit(new.encode("utf-8"))
+            new_url = urllib.parse.urlsplit(new)
 
             # we handle the known URL schemes
             if new_url.scheme == "xmpp":
                 location = self.getPagePathFromURI(new)
                 if location is None:
                     log.warning(
-                        _(u"ignoring redirection, no page found to handle this URI: "
-                          u"{uri}").format(uri=new))
+                        _("ignoring redirection, no page found to handle this URI: "
+                          "{uri}").format(uri=new))
                     continue
                 request_data = self._getRequestData(location)
                 if old:
@@ -287,29 +283,29 @@
                 # direct redirection
                 if new_url.netloc:
                     raise NotImplementedError(
-                        u"netloc ({netloc}) is not implemented yet for "
-                        u"url_redirections_dict, it is not possible to redirect to an "
-                        u"external website".format(netloc=new_url.netloc))
-                location = urlparse.urlunsplit(
+                        "netloc ({netloc}) is not implemented yet for "
+                        "url_redirections_dict, it is not possible to redirect to an "
+                        "external website".format(netloc=new_url.netloc))
+                location = urllib.parse.urlunsplit(
                     ("", "", new_url.path, new_url.query, new_url.fragment)
-                ).decode("utf-8")
+                )
                 request_data = self._getRequestData(location)
                 if old:
                     self.inv_redirections[location] = old
 
-            elif new_url.scheme in ("file"):
+            elif new_url.scheme == "file":
                 # file or directory
                 if new_url.netloc:
                     raise NotImplementedError(
-                        u"netloc ({netloc}) is not implemented for url redirection to "
-                        u"file system, it is not possible to redirect to an external "
+                        "netloc ({netloc}) is not implemented for url redirection to "
+                        "file system, it is not possible to redirect to an external "
                         "host".format(
                             netloc=new_url.netloc))
-                path = urllib.unquote(new_url.path)
+                path = urllib.parse.unquote(new_url.path)
                 if not os.path.isabs(path):
                     raise ValueError(
-                        u"file redirection must have an absolute path: e.g. "
-                        u"file:/path/to/my/file")
+                        "file redirection must have an absolute path: e.g. "
+                        "file:/path/to/my/file")
                 # for file redirection, we directly put child here
                 segments, __, last_segment = old.rpartition("/")
                 url_segments = segments.split("/") if segments else []
@@ -322,27 +318,27 @@
                     ProtectedFile if new_data.get("protected", True) else static.File
                 )
                 current.putChild(
-                    last_segment,
+                    last_segment.encode('utf-8'),
                     resource_class(path, defaultType="application/octet-stream")
                 )
-                log.info(u"[{host_name}] Added redirection from /{old} to file system "
-                         u"path {path}".format(host_name=self.host_name,
-                                               old=old.decode("utf-8"),
-                                               path=path.decode("utf-8")))
+                log.info("[{host_name}] Added redirection from /{old} to file system "
+                         "path {path}".format(host_name=self.host_name,
+                                               old=old,
+                                               path=path))
                 continue  # we don't want to use redirection system, so we continue here
 
             else:
                 raise NotImplementedError(
-                    u"{scheme}: scheme is not managed for url_redirections_dict".format(
+                    "{scheme}: scheme is not managed for url_redirections_dict".format(
                         scheme=new_url.scheme
                     )
                 )
 
             self.redirections[old] = request_data
             if not old:
-                log.info(_(u"[{host_name}] Root URL redirected to {uri}")
+                log.info(_("[{host_name}] Root URL redirected to {uri}")
                     .format(host_name=self.host_name,
-                            uri=request_data[1].decode("utf-8")))
+                            uri=request_data[1]))
 
         # the default root URL, if not redirected
         if not "" in self.redirections:
@@ -353,13 +349,13 @@
         main_menu = []
         for menu in menus:
             if not menu:
-                msg = _(u"menu item can't be empty")
+                msg = _("menu item can't be empty")
                 log.error(msg)
                 raise ValueError(msg)
             elif isinstance(menu, list):
                 if len(menu) != 2:
                     msg = _(
-                        u"menu item as list must be in the form [page_name, absolue URL]"
+                        "menu item as list must be in the form [page_name, absolue URL]"
                     )
                     log.error(msg)
                     raise ValueError(msg)
@@ -369,8 +365,8 @@
                 try:
                     url = self.getPageByName(page_name).url
                 except KeyError as e:
-                    log_msg = _(u"Can'find a named page ({msg}), please check "
-                                u"menu_json in configuration.").format(msg=e.args[0])
+                    log_msg = _("Can'find a named page ({msg}), please check "
+                                "menu_json in configuration.").format(msg=e.args[0])
                     log.error(log_msg)
                     raise exceptions.ConfigError(log_msg)
             main_menu.append((page_name, url))
@@ -385,7 +381,7 @@
         """
         if lower:
             url = url.lower()
-        return "/".join((p for p in url.encode("utf-8").split("/") if p))
+        return "/".join((p for p in url.split("/") if p))
 
     def _getRequestData(self, uri):
         """Return data needed to redirect request
@@ -397,22 +393,22 @@
             path as in Request.path
             args as in Request.args
         """
-        uri = uri.encode("utf-8")
+        uri = uri
         # XXX: we reuse code from twisted.web.http.py here
         #      as we need to have the same behaviour
-        x = uri.split(b"?", 1)
+        x = uri.split("?", 1)
 
         if len(x) == 1:
             path = uri
             args = {}
         else:
             path, argstring = x
-            args = http.parse_qs(argstring, 1)
+            args = urllib.parse.parse_qs(argstring, True)
 
         # XXX: splitted path case must not be changed, as it may be significant
         #      (e.g. for blog items)
         return (
-            self._normalizeURL(path.decode("utf-8"), lower=False).split("/"),
+            self._normalizeURL(path, lower=False).split("/"),
             uri,
             path,
             args,
@@ -435,10 +431,10 @@
             try:
                 __, uri, __, __ = request_data
             except ValueError:
-                uri = u""
-            log.error(D_( u"recursive redirection, please fix this URL:\n"
-                          u"{old} ==> {new}").format(
-                          old=request.uri.decode("utf-8"), new=uri.decode("utf-8")))
+                uri = ""
+            log.error(D_( "recursive redirection, please fix this URL:\n"
+                          "{old} ==> {new}").format(
+                          old=request.uri.decode("utf-8"), new=uri))
             return web_resource.NoResource()
 
         request._redirected = True  # here to avoid recursive redirections
@@ -450,29 +446,31 @@
                 except KeyError:
                     log.error(
                         _(
-                            u'Can\'t find page named "{name}" requested in redirection'
+                            'Can\'t find page named "{name}" requested in redirection'
                         ).format(name=request_data["page"])
                     )
                     return web_resource.NoResource()
-                request.postpath = request_data["path_args"][:] + request.postpath
+                path_args = [pa.encode('utf-8') for pa in request_data["path_args"]]
+                request.postpath = path_args + request.postpath
 
                 try:
                     request.args.update(request_data["query_args"])
                 except (TypeError, ValueError):
                     log.error(
-                        _(u"Invalid args in redirection: {query_args}").format(
+                        _("Invalid args in redirection: {query_args}").format(
                             query_args=request_data["query_args"]
                         )
                     )
                     return web_resource.NoResource()
                 return page
             else:
-                raise exceptions.InternalError(u"unknown request_data type")
+                raise exceptions.InternalError("unknown request_data type")
         else:
             path_list, uri, path, args = request_data
+            path_list = [p.encode('utf-8') for p in path_list]
             log.debug(
-                u"Redirecting URL {old} to {new}".format(
-                    old=request.uri.decode("utf-8"), new=uri.decode("utf-8")
+                "Redirecting URL {old} to {new}".format(
+                    old=request.uri.decode('utf-8'), new=uri
                 )
             )
             # we change the request to reflect the new url
@@ -520,7 +518,7 @@
     def getChildWithDefault(self, name, request):
         # XXX: this method is overriden only for root url
         #      which is the only ones who need to be handled before other children
-        if name == "" and not request.postpath:
+        if name == b"" and not request.postpath:
             return self._redirect(request, self.redirections[""])
         return super(LiberviaRootResource, self).getChildWithDefault(name, request)
 
@@ -531,8 +529,8 @@
             # if nothing was found, we try our luck with redirections
             # XXX: we want redirections to happen only if everything else failed
             path_elt = request.prepath + request.postpath
-            for idx in xrange(len(path_elt), 0, -1):
-                test_url = "/".join(path_elt[:idx]).lower()
+            for idx in range(len(path_elt), 0, -1):
+                test_url = b"/".join(path_elt[:idx]).decode('utf-8').lower()
                 if test_url in self.redirections:
                     request_data = self.redirections[test_url]
                     request.postpath = path_elt[idx:]
@@ -542,6 +540,8 @@
 
     def putChild(self, path, resource):
         """Add a child to the root resource"""
+        if not isinstance(path, bytes):
+            raise ValueError("path must be specified in bytes")
         if not isinstance(resource, web_resource.EncodingResourceWrapper):
             # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders)
             resource = web_resource.EncodingResourceWrapper(
@@ -562,639 +562,6 @@
         return f
 
 
-class JSONRPCMethodManager(jsonrpc.JSONRPC):
-    def __init__(self, sat_host):
-        jsonrpc.JSONRPC.__init__(self)
-        self.sat_host = sat_host
-
-    def _bridgeCallEb(self, failure_):
-        """Raise a jsonrpclib failure for the frontend"""
-        return failure.Failure(
-                jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, failure_.value.classname)
-            )
-
-    def asyncBridgeCall(self, method_name, *args, **kwargs):
-        d = self.sat_host.bridgeCall(method_name, *args, **kwargs)
-        d.addErrback(self._bridgeCallEb)
-        return d
-
-
-class MethodHandler(JSONRPCMethodManager):
-    def __init__(self, sat_host):
-        JSONRPCMethodManager.__init__(self, sat_host)
-
-    def render(self, request):
-        self.session = request.getSession()
-        profile = session_iface.ISATSession(self.session).profile
-        if not profile:
-            # user is not identified, we return a jsonrpc fault
-            parsed = jsonrpclib.loads(request.content.read())
-            fault = jsonrpclib.Fault(
-                C.ERRNUM_LIBERVIA, C.NOT_ALLOWED
-            )  # FIXME: define some standard error codes for libervia
-            return jsonrpc.JSONRPC._cbRender(
-                self, fault, request, parsed.get("id"), parsed.get("jsonrpc")
-            )  # pylint: disable=E1103
-        return jsonrpc.JSONRPC.render(self, request)
-
-    @defer.inlineCallbacks
-    def jsonrpc_getVersion(self):
-        """Return SàT version"""
-        try:
-            defer.returnValue(self._version_cache)
-        except AttributeError:
-            self._version_cache = yield self.sat_host.bridgeCall("getVersion")
-            defer.returnValue(self._version_cache)
-
-    def jsonrpc_getLiberviaVersion(self):
-        """Return Libervia version"""
-        return self.sat_host.full_version
-
-    def jsonrpc_disconnect(self):
-        """Disconnect the profile"""
-        sat_session = session_iface.ISATSession(self.session)
-        profile = sat_session.profile
-        self.sat_host.bridgeCall("disconnect", profile)
-
-    def jsonrpc_getContacts(self):
-        """Return all passed args."""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getContacts", profile)
-
-    @defer.inlineCallbacks
-    def jsonrpc_addContact(self, entity, name, groups):
-        """Subscribe to contact presence, and add it to the given groups"""
-        profile = session_iface.ISATSession(self.session).profile
-        yield self.sat_host.bridgeCall("addContact", entity, profile)
-        yield self.sat_host.bridgeCall("updateContact", entity, name, groups, profile)
-
-    def jsonrpc_delContact(self, entity):
-        """Remove contact from contacts list"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("delContact", entity, profile)
-
-    def jsonrpc_updateContact(self, entity, name, groups):
-        """Update contact's roster item"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("updateContact", entity, name, groups, profile)
-
-    def jsonrpc_subscription(self, sub_type, entity):
-        """Confirm (or infirm) subscription,
-        and setup user roster in case of subscription"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("subscription", sub_type, entity, profile)
-
-    def jsonrpc_getWaitingSub(self):
-        """Return list of room already joined by user"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getWaitingSub", profile)
-
-    def jsonrpc_setStatus(self, presence, status):
-        """Change the presence and/or status
-        @param presence: value from ("", "chat", "away", "dnd", "xa")
-        @param status: any string to describe your status
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "setPresence", "", presence, {"": status}, profile
-        )
-
-    def jsonrpc_messageSend(self, to_jid, msg, subject, type_, extra={}):
-        """send message"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall(
-            "messageSend", to_jid, msg, subject, type_, extra, profile
-        )
-
-    ## PubSub ##
-
-    def jsonrpc_psNodeDelete(self, service, node):
-        """Delete a whole node
-
-        @param service (unicode): service jid
-        @param node (unicode): node to delete
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall("psNodeDelete", service, node, profile)
-
-    # def jsonrpc_psRetractItem(self, service, node, item, notify):
-    #     """Delete a whole node
-
-    #     @param service (unicode): service jid
-    #     @param node (unicode): node to delete
-    #     @param items (iterable): id of item to retract
-    #     @param notify (bool): True if notification is required
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     return self.asyncBridgeCall("psRetractItem", service, node, item, notify,
-    #                                 profile)
-
-    # def jsonrpc_psRetractItems(self, service, node, items, notify):
-    #     """Delete a whole node
-
-    #     @param service (unicode): service jid
-    #     @param node (unicode): node to delete
-    #     @param items (iterable): ids of items to retract
-    #     @param notify (bool): True if notification is required
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     return self.asyncBridgeCall("psRetractItems", service, node, items, notify,
-    #                                 profile)
-
-    ## microblogging ##
-
-    def jsonrpc_mbSend(self, service, node, mb_data):
-        """Send microblog data
-
-        @param service (unicode): service jid or empty string to use profile's microblog
-        @param node (unicode): publishing node, or empty string to use microblog node
-        @param mb_data(dict): microblog data
-        @return: a deferred
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall("mbSend", service, node, mb_data, profile)
-
-    def jsonrpc_mbRetract(self, service, node, items):
-        """Delete a whole node
-
-        @param service (unicode): service jid, empty string for PEP
-        @param node (unicode): node to delete, empty string for default node
-        @param items (iterable): ids of items to retract
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall("mbRetract", service, node, items, profile)
-
-    def jsonrpc_mbGet(self, service_jid, node, max_items, item_ids, extra):
-        """Get last microblogs from publisher_jid
-
-        @param service_jid (unicode): pubsub service, usually publisher jid
-        @param node(unicode): mblogs node, or empty string to get the defaut one
-        @param max_items (int): maximum number of item to get or C.NO_LIMIT to get
-            everything
-        @param item_ids (list[unicode]): list of item IDs
-        @param rsm (dict): TODO
-        @return: a deferred couple with the list of items and metadatas.
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall(
-            "mbGet", service_jid, node, max_items, item_ids, extra, profile
-        )
-
-    def jsonrpc_mbGetFromMany(self, publishers_type, publishers, max_items, extra):
-        """Get many blog nodes at once
-
-        @param publishers_type (unicode): one of "ALL", "GROUP", "JID"
-        @param publishers (tuple(unicode)): tuple of publishers (empty list for all,
-            list of groups or list of jids)
-        @param max_items (int): maximum number of item to get or C.NO_LIMIT to get
-            everything
-        @param extra (dict): TODO
-        @return (str): RT Deferred session id
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "mbGetFromMany", publishers_type, publishers, max_items, extra, profile
-        )
-
-    def jsonrpc_mbGetFromManyRTResult(self, rt_session):
-        """Get results from RealTime mbGetFromMany session
-
-        @param rt_session (str): RT Deferred session id
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall("mbGetFromManyRTResult", rt_session, profile)
-
-    def jsonrpc_mbGetFromManyWithComments(
-        self,
-        publishers_type,
-        publishers,
-        max_items,
-        max_comments,
-        rsm_dict,
-        rsm_comments_dict,
-    ):
-        """Helper method to get the microblogs and their comments in one shot
-
-        @param publishers_type (str): type of the list of publishers (one of "GROUP" or
-            "JID" or "ALL")
-        @param publishers (list): list of publishers, according to publishers_type
-            (list of groups or list of jids)
-        @param max_items (int): optional limit on the number of retrieved items.
-        @param max_comments (int): maximum number of comments to retrieve
-        @param rsm_dict (dict): RSM data for initial items only
-        @param rsm_comments_dict (dict): RSM data for comments only
-        @param profile_key: profile key
-        @return (str): RT Deferred session id
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "mbGetFromManyWithComments",
-            publishers_type,
-            publishers,
-            max_items,
-            max_comments,
-            rsm_dict,
-            rsm_comments_dict,
-            profile,
-        )
-
-    def jsonrpc_mbGetFromManyWithCommentsRTResult(self, rt_session):
-        """Get results from RealTime mbGetFromManyWithComments session
-
-        @param rt_session (str): RT Deferred session id
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall(
-            "mbGetFromManyWithCommentsRTResult", rt_session, profile
-        )
-
-    # def jsonrpc_sendMblog(self, type_, dest, text, extra={}):
-    #     """ Send microblog message
-    #     @param type_ (unicode): one of "PUBLIC", "GROUP"
-    #     @param dest (tuple(unicode)): recipient groups (ignored for "PUBLIC")
-    #     @param text (unicode): microblog's text
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     extra['allow_comments'] = 'True'
-
-    #     if not type_:  # auto-detect
-    #         type_ = "PUBLIC" if dest == [] else "GROUP"
-
-    #     if type_ in ("PUBLIC", "GROUP") and text:
-    #         if type_ == "PUBLIC":
-    #             #This text if for the public microblog
-    #             log.debug("sending public blog")
-    #             return self.sat_host.bridge.sendGroupBlog("PUBLIC", (), text, extra,
-    #                                                       profile)
-    #         else:
-    #             log.debug("sending group blog")
-    #             dest = dest if isinstance(dest, list) else [dest]
-    #             return self.sat_host.bridge.sendGroupBlog("GROUP", dest, text, extra,
-    #                                                       profile)
-    #     else:
-    #         raise Exception("Invalid data")
-
-    # def jsonrpc_deleteMblog(self, pub_data, comments):
-    #     """Delete a microblog node
-    #     @param pub_data: a tuple (service, comment node identifier, item identifier)
-    #     @param comments: comments node identifier (for main item) or False
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     return self.sat_host.bridge.deleteGroupBlog(pub_data, comments if comments
-    #         else '', profile)
-
-    # def jsonrpc_updateMblog(self, pub_data, comments, message, extra={}):
-    #     """Modify a microblog node
-    #     @param pub_data: a tuple (service, comment node identifier, item identifier)
-    #     @param comments: comments node identifier (for main item) or False
-    #     @param message: new message
-    #     @param extra: dict which option name as key, which can be:
-    #         - allow_comments: True to accept an other level of comments, False else
-    #               (default: False)
-    #         - rich: if present, contain rich text in currently selected syntax
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     if comments:
-    #         extra['allow_comments'] = 'True'
-    #     return self.sat_host.bridge.updateGroupBlog(pub_data, comments if comments
-    #         else '', message, extra, profile)
-
-    # def jsonrpc_sendMblogComment(self, node, text, extra={}):
-    #     """ Send microblog message
-    #     @param node: url of the comments node
-    #     @param text: comment
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     if node and text:
-    #         return self.sat_host.bridge.sendGroupBlogComment(node, text, extra, profile)
-    #     else:
-    #         raise Exception("Invalid data")
-
-    # def jsonrpc_getMblogs(self, publisher_jid, item_ids, max_items=C.RSM_MAX_ITEMS):
-    #     """Get specified microblogs posted by a contact
-    #     @param publisher_jid: jid of the publisher
-    #     @param item_ids: list of microblogs items IDs
-    #     @return list of microblog data (dict)"""
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     d = self.asyncBridgeCall("getGroupBlogs", publisher_jid, item_ids, {'max_': unicode(max_items)}, False, profile)
-    #     return d
-
-    # def jsonrpc_getMblogsWithComments(self, publisher_jid, item_ids, max_comments=C.RSM_MAX_COMMENTS):
-    #     """Get specified microblogs posted by a contact and their comments
-    #     @param publisher_jid: jid of the publisher
-    #     @param item_ids: list of microblogs items IDs
-    #     @return list of couple (microblog data, list of microblog data)"""
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     d = self.asyncBridgeCall("getGroupBlogsWithComments", publisher_jid, item_ids, {}, max_comments, profile)
-    #     return d
-
-    # def jsonrpc_getMassiveMblogs(self, publishers_type, publishers, rsm=None):
-    #     """Get lasts microblogs posted by several contacts at once
-
-    #     @param publishers_type (unicode): one of "ALL", "GROUP", "JID"
-    #     @param publishers (tuple(unicode)): tuple of publishers (empty list for all, list of groups or list of jids)
-    #     @param rsm (dict): TODO
-    #     @return: dict{unicode: list[dict])
-    #         key: publisher's jid
-    #         value: list of microblog data (dict)
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     if rsm is None:
-    #         rsm = {'max_': unicode(C.RSM_MAX_ITEMS)}
-    #     d = self.asyncBridgeCall("getMassiveGroupBlogs", publishers_type, publishers, rsm, profile)
-    #     self.sat_host.bridge.massiveSubscribeGroupBlogs(publishers_type, publishers, profile)
-    #     return d
-
-    # def jsonrpc_getMblogComments(self, service, node, rsm=None):
-    #     """Get all comments of given node
-    #     @param service: jid of the service hosting the node
-    #     @param node: comments node
-    #     """
-    #     profile = session_iface.ISATSession(self.session).profile
-    #     if rsm is None:
-    #         rsm = {'max_': unicode(C.RSM_MAX_COMMENTS)}
-    #     d = self.asyncBridgeCall("getGroupBlogComments", service, node, rsm, profile)
-    #     return d
-
-    def jsonrpc_getPresenceStatuses(self):
-        """Get Presence information for connected contacts"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getPresenceStatuses", profile)
-
-    def jsonrpc_historyGet(self, from_jid, to_jid, size, between, search=""):
-        """Return history for the from_jid/to_jid couple"""
-        sat_session = session_iface.ISATSession(self.session)
-        profile = sat_session.profile
-        sat_jid = sat_session.jid
-        if not sat_jid:
-            raise exceptions.InternalError("session jid should be set")
-        if (
-            jid.JID(from_jid).userhost() != sat_jid.userhost()
-            and jid.JID(to_jid).userhost() != sat_jid.userhost()
-        ):
-            log.error(
-                u"Trying to get history from a different jid (given (browser): {}, real "
-                u"(backend): {}), maybe a hack attempt ?".format( from_jid, sat_jid))
-            return {}
-        d = self.asyncBridgeCall(
-            "historyGet", from_jid, to_jid, size, between, search, profile)
-
-        def show(result_dbus):
-            result = []
-            for line in result_dbus:
-                # XXX: we have to do this stupid thing because Python D-Bus use its own
-                #      types instead of standard types and txJsonRPC doesn't accept
-                #      D-Bus types, resulting in a empty query
-                uuid, timestamp, from_jid, to_jid, message, subject, mess_type, extra = (
-                    line
-                )
-                result.append(
-                    (
-                        unicode(uuid),
-                        float(timestamp),
-                        unicode(from_jid),
-                        unicode(to_jid),
-                        dict(message),
-                        dict(subject),
-                        unicode(mess_type),
-                        dict(extra),
-                    )
-                )
-            return result
-
-        d.addCallback(show)
-        return d
-
-    def jsonrpc_mucJoin(self, room_jid, nick):
-        """Join a Multi-User Chat room
-
-        @param room_jid (unicode): room JID or empty string to generate a unique name
-        @param nick (unicode): user nick
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        d = self.asyncBridgeCall("joinMUC", room_jid, nick, {}, profile)
-        return d
-
-    def jsonrpc_inviteMUC(self, contact_jid, room_jid):
-        """Invite a user to a Multi-User Chat room
-
-        @param contact_jid (unicode): contact to invite
-        @param room_jid (unicode): room JID or empty string to generate a unique name
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        room_id = room_jid.split("@")[0]
-        service = room_jid.split("@")[1]
-        return self.sat_host.bridgeCall(
-            "inviteMUC", contact_jid, service, room_id, {}, profile
-        )
-
-    def jsonrpc_mucLeave(self, room_jid):
-        """Quit a Multi-User Chat room"""
-        profile = session_iface.ISATSession(self.session).profile
-        try:
-            room_jid = jid.JID(room_jid)
-        except:
-            log.warning("Invalid room jid")
-            return
-        return self.sat_host.bridgeCall("mucLeave", room_jid.userhost(), profile)
-
-    def jsonrpc_mucGetRoomsJoined(self):
-        """Return list of room already joined by user"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("mucGetRoomsJoined", profile)
-
-    def jsonrpc_mucGetDefaultService(self):
-        """@return: the default MUC"""
-        d = self.asyncBridgeCall("mucGetDefaultService")
-        return d
-
-    def jsonrpc_launchTarotGame(self, other_players, room_jid=""):
-        """Create a room, invite the other players and start a Tarot game.
-
-        @param other_players (list[unicode]): JIDs of the players to play with
-        @param room_jid (unicode): room JID or empty string to generate a unique name
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "tarotGameLaunch", other_players, room_jid, profile
-        )
-
-    def jsonrpc_getTarotCardsPaths(self):
-        """Give the path of all the tarot cards"""
-        _join = os.path.join
-        _media_dir = _join(self.sat_host.media_dir, "")
-        return map(
-            lambda x: _join(C.MEDIA_DIR, x[len(_media_dir) :]),
-            glob.glob(_join(_media_dir, C.CARDS_DIR, "*_*.png")),
-        )
-
-    def jsonrpc_tarotGameReady(self, player, referee):
-        """Tell to the server that we are ready to start the game"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("tarotGameReady", player, referee, profile)
-
-    def jsonrpc_tarotGamePlayCards(self, player_nick, referee, cards):
-        """Tell to the server the cards we want to put on the table"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "tarotGamePlayCards", player_nick, referee, cards, profile
-        )
-
-    def jsonrpc_launchRadioCollective(self, invited, room_jid=""):
-        """Create a room, invite people, and start a radio collective.
-
-        @param invited (list[unicode]): JIDs of the contacts to play with
-        @param room_jid (unicode): room JID or empty string to generate a unique name
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("radiocolLaunch", invited, room_jid, profile)
-
-    def jsonrpc_getEntitiesData(self, jids, keys):
-        """Get cached data for several entities at once
-
-        @param jids: list jids from who we wants data, or empty list for all jids in cache
-        @param keys: name of data we want (list)
-        @return: requested data"""
-        if not C.ALLOWED_ENTITY_DATA.issuperset(keys):
-            raise exceptions.PermissionError(
-                "Trying to access unallowed data (hack attempt ?)"
-            )
-        profile = session_iface.ISATSession(self.session).profile
-        try:
-            return self.sat_host.bridgeCall("getEntitiesData", jids, keys, profile)
-        except Exception as e:
-            raise failure.Failure(jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, unicode(e)))
-
-    def jsonrpc_getEntityData(self, jid, keys):
-        """Get cached data for an entity
-
-        @param jid: jid of contact from who we want data
-        @param keys: name of data we want (list)
-        @return: requested data"""
-        if not C.ALLOWED_ENTITY_DATA.issuperset(keys):
-            raise exceptions.PermissionError(
-                "Trying to access unallowed data (hack attempt ?)"
-            )
-        profile = session_iface.ISATSession(self.session).profile
-        try:
-            return self.sat_host.bridgeCall("getEntityData", jid, keys, profile)
-        except Exception as e:
-            raise failure.Failure(jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, unicode(e)))
-
-    def jsonrpc_getCard(self, jid_):
-        """Get VCard for entiry
-        @param jid_: jid of contact from who we want data
-        @return: id to retrieve the profile"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getCard", jid_, profile)
-
-    @defer.inlineCallbacks
-    def jsonrpc_avatarGet(self, entity, cache_only, hash_only):
-        session_data = session_iface.ISATSession(self.session)
-        profile = session_data.profile
-        # profile_uuid = session_data.uuid
-        avatar = yield self.asyncBridgeCall(
-            "avatarGet", entity, cache_only, hash_only, profile
-        )
-        if hash_only:
-            defer.returnValue(avatar)
-        else:
-            filename = os.path.basename(avatar)
-            avatar_url = os.path.join(session_data.cache_dir, filename)
-            defer.returnValue(avatar_url)
-
-    def jsonrpc_getAccountDialogUI(self):
-        """Get the dialog for managing user account
-        @return: XML string of the XMLUI"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getAccountDialogUI", profile)
-
-    def jsonrpc_getParamsUI(self):
-        """Return the parameters XML for profile"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.asyncBridgeCall("getParamsUI", C.SECURITY_LIMIT, C.APP_NAME, profile)
-
-    def jsonrpc_asyncGetParamA(self, param, category, attribute="value"):
-        """Return the parameter value for profile"""
-        profile = session_iface.ISATSession(self.session).profile
-        if category == "Connection":
-            # we need to manage the followings params here, else SECURITY_LIMIT would
-            # block them
-            if param == "JabberID":
-                return self.asyncBridgeCall(
-                    "asyncGetParamA", param, category, attribute, profile_key=profile
-                )
-            elif param == "autoconnect":
-                return defer.succeed(C.BOOL_TRUE)
-        d = self.asyncBridgeCall(
-            "asyncGetParamA",
-            param,
-            category,
-            attribute,
-            C.SECURITY_LIMIT,
-            profile_key=profile,
-        )
-        return d
-
-    def jsonrpc_setParam(self, name, value, category):
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "setParam", name, value, category, C.SECURITY_LIMIT, profile
-        )
-
-    def jsonrpc_launchAction(self, callback_id, data):
-        # FIXME: any action can be launched, this can be a huge security issue if
-        #        callback_id can be guessed a security system with authorised
-        #        callback_id must be implemented, similar to the one for authorised params
-        profile = session_iface.ISATSession(self.session).profile
-        d = self.asyncBridgeCall("launchAction", callback_id, data, profile)
-        return d
-
-    def jsonrpc_chatStateComposing(self, to_jid_s):
-        """Call the method to process a "composing" state.
-        @param to_jid_s: contact the user is composing to
-        """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("chatStateComposing", to_jid_s, profile)
-
-    def jsonrpc_getNewAccountDomain(self):
-        """@return: the domain for new account creation"""
-        d = self.asyncBridgeCall("getNewAccountDomain")
-        return d
-
-    def jsonrpc_syntaxConvert(
-        self, text, syntax_from=C.SYNTAX_XHTML, syntax_to=C.SYNTAX_CURRENT
-    ):
-        """ Convert a text between two syntaxes
-        @param text: text to convert
-        @param syntax_from: source syntax (e.g. "markdown")
-        @param syntax_to: dest syntax (e.g.: "XHTML")
-        @param safe: clean resulting XHTML to avoid malicious code if True (forced here)
-        @return: converted text """
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall(
-            "syntaxConvert", text, syntax_from, syntax_to, True, profile
-        )
-
-    def jsonrpc_getLastResource(self, jid_s):
-        """Get the last active resource of that contact."""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getLastResource", jid_s, profile)
-
-    def jsonrpc_getFeatures(self):
-        """Return the available features in the backend for profile"""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("getFeatures", profile)
-
-    def jsonrpc_skipOTR(self):
-        """Tell the backend to leave OTR handling to Libervia."""
-        profile = session_iface.ISATSession(self.session).profile
-        return self.sat_host.bridgeCall("skipOTR", profile)
-
-    def jsonrpc_namespacesGet(self):
-        return self.sat_host.bridgeCall("namespacesGet")
-
-
 class WaitingRequests(dict):
     def setRequest(self, request, profile, register_with_ext_jid=False):
         """Add the given profile to the waiting list.
@@ -1237,508 +604,6 @@
         return self[profile][2] if profile in self else None
 
 
-class Register(JSONRPCMethodManager):
-    """This class manage the registration procedure with SàT
-    It provide an api for the browser, check password and setup the web server"""
-
-    def __init__(self, sat_host):
-        JSONRPCMethodManager.__init__(self, sat_host)
-        self.profiles_waiting = {}
-        self.request = None
-
-    def render(self, request):
-        """
-        Render method with some hacks:
-           - if login is requested, try to login with form data
-           - except login, every method is jsonrpc
-           - user doesn't need to be authentified for explicitely listed methods,
-             but must be for all others
-        """
-        if request.postpath == ["login"]:
-            return self.loginOrRegister(request)
-        _session = request.getSession()
-        parsed = jsonrpclib.loads(request.content.read())
-        method = parsed.get("method")  # pylint: disable=E1103
-        if method not in ["getSessionMetadata", "registerParams", "menusGet"]:
-            # if we don't call these methods, we need to be identified
-            profile = session_iface.ISATSession(_session).profile
-            if not profile:
-                # user is not identified, we return a jsonrpc fault
-                fault = jsonrpclib.Fault(
-                    C.ERRNUM_LIBERVIA, C.NOT_ALLOWED
-                )  # FIXME: define some standard error codes for libervia
-                return jsonrpc.JSONRPC._cbRender(
-                    self, fault, request, parsed.get("id"), parsed.get("jsonrpc")
-                )  # pylint: disable=E1103
-        self.request = request
-        return jsonrpc.JSONRPC.render(self, request)
-
-    def loginOrRegister(self, request):
-        """This method is called with the POST information from the registering form.
-
-        @param request: request of the register form
-        @return: a constant indicating the state:
-            - C.BAD_REQUEST: something is wrong in the request (bad arguments)
-            - a return value from self._loginAccount or self._registerNewAccount
-        """
-        try:
-            submit_type = request.args["submit_type"][0]
-        except KeyError:
-            return C.BAD_REQUEST
-
-        if submit_type == "register":
-            self._registerNewAccount(request)
-            return server.NOT_DONE_YET
-        elif submit_type == "login":
-            self._loginAccount(request)
-            return server.NOT_DONE_YET
-        return Exception("Unknown submit type")
-
-    @defer.inlineCallbacks
-    def _registerNewAccount(self, request):
-        try:
-            login = request.args["register_login"][0]
-            password = request.args["register_password"][0]
-            email = request.args["email"][0]
-        except KeyError:
-            request.write(C.BAD_REQUEST)
-            request.finish()
-            return
-        status = yield self.sat_host.registerNewAccount(request, login, password, email)
-        request.write(status)
-        request.finish()
-
-    @defer.inlineCallbacks
-    def _loginAccount(self, request):
-        """Try to authenticate the user with the request information.
-
-        will write to request a constant indicating the state:
-            - C.PROFILE_LOGGED: profile is connected
-            - C.PROFILE_LOGGED_EXT_JID: profile is connected and an external jid has
-                been used
-            - C.SESSION_ACTIVE: session was already active
-            - C.BAD_REQUEST: something is wrong in the request (bad arguments)
-            - C.PROFILE_AUTH_ERROR: either the profile (login) or the profile password
-                is wrong
-            - C.XMPP_AUTH_ERROR: the profile is authenticated but the XMPP password
-                is wrong
-            - C.ALREADY_WAITING: a request has already been submitted for this profile,
-                C.PROFILE_LOGGED_EXT_JID)
-            - C.NOT_CONNECTED: connection has not been established
-        the request will then be finished
-        @param request: request of the register form
-        """
-        try:
-            login = request.args["login"][0]
-            password = request.args["login_password"][0]
-        except KeyError:
-            request.write(C.BAD_REQUEST)
-            request.finish()
-            return
-
-        assert login
-
-        try:
-            status = yield self.sat_host.connect(request, login, password)
-        except (
-            exceptions.DataError,
-            exceptions.ProfileUnknownError,
-            exceptions.PermissionError,
-        ):
-            request.write(C.PROFILE_AUTH_ERROR)
-            request.finish()
-            return
-        except exceptions.NotReady:
-            request.write(C.ALREADY_WAITING)
-            request.finish()
-            return
-        except exceptions.TimeOutError:
-            request.write(C.NO_REPLY)
-            request.finish()
-            return
-        except exceptions.InternalError as e:
-            request.write(e.message)
-            request.finish()
-            return
-        except exceptions.ConflictError:
-            request.write(C.SESSION_ACTIVE)
-            request.finish()
-            return
-        except ValueError as e:
-            if e.message in (C.PROFILE_AUTH_ERROR, C.XMPP_AUTH_ERROR):
-                request.write(e.message)
-                request.finish()
-                return
-            else:
-                raise e
-
-        assert status
-        request.write(status)
-        request.finish()
-
-    def jsonrpc_isConnected(self):
-        _session = self.request.getSession()
-        profile = session_iface.ISATSession(_session).profile
-        return self.sat_host.bridgeCall("isConnected", profile)
-
-    def jsonrpc_connect(self):
-        _session = self.request.getSession()
-        profile = session_iface.ISATSession(_session).profile
-        if self.waiting_profiles.getRequest(profile):
-            raise jsonrpclib.Fault(
-                1, C.ALREADY_WAITING
-            )  # FIXME: define some standard error codes for libervia
-        self.waiting_profiles.setRequest(self.request, profile)
-        self.sat_host.bridgeCall("connect", profile)
-        return server.NOT_DONE_YET
-
-    def jsonrpc_getSessionMetadata(self):
-        """Return metadata useful on session start
-
-        @return (dict): metadata which can have the following keys:
-            "plugged" (bool): True if a profile is already plugged
-            "warning" (unicode): a security warning message if plugged is False and if
-                it make sense.
-                This key may not be present.
-            "allow_registration" (bool): True if registration is allowed
-                this key is only present if profile is unplugged
-        @return: a couple (registered, message) with:
-        - registered:
-        - message:
-        """
-        metadata = {}
-        _session = self.request.getSession()
-        profile = session_iface.ISATSession(_session).profile
-        if profile:
-            metadata["plugged"] = True
-        else:
-            metadata["plugged"] = False
-            metadata["warning"] = self._getSecurityWarning()
-            metadata["allow_registration"] = self.sat_host.options["allow_registration"]
-        return metadata
-
-    def jsonrpc_registerParams(self):
-        """Register the frontend specific parameters"""
-        # params = """<params><individual>...</category></individual>"""
-        # self.sat_host.bridge.paramsRegisterApp(params, C.SECURITY_LIMIT, C.APP_NAME)
-
-    def jsonrpc_menusGet(self):
-        """Return the parameters XML for profile"""
-        # XXX: we put this method in Register because we get menus before being logged
-        return self.sat_host.bridgeCall("menusGet", "", C.SECURITY_LIMIT)
-
-    def _getSecurityWarning(self):
-        """@return: a security warning message, or None if the connection is secure"""
-        if (
-            self.request.URLPath().scheme == "https"
-            or not self.sat_host.options["security_warning"]
-        ):
-            return None
-        text = (
-            "<p>"
-            + D_("You are about to connect to an unsecure service.")
-            + "</p><p>&nbsp;</p><p>"
-        )
-
-        if self.sat_host.options["connection_type"] == "both":
-            new_port = (
-                (":%s" % self.sat_host.options["port_https_ext"])
-                if self.sat_host.options["port_https_ext"] != HTTPS_PORT
-                else ""
-            )
-            url = "https://%s" % self.request.URLPath().netloc.replace(
-                ":%s" % self.sat_host.options["port"], new_port
-            )
-            text += D_(
-                "Please read our %(faq_prefix)ssecurity notice%(faq_suffix)s regarding HTTPS"
-            ) % {
-                "faq_prefix": '<a href="http://salut-a-toi.org/faq.html#https" target="#">',
-                "faq_suffix": "</a>",
-            }
-            text += "</p><p>" + D_("and use the secure version of this website:")
-            text += '</p><p>&nbsp;</p><p align="center"><a href="%(url)s">%(url)s</a>' % {
-                "url": url
-            }
-        else:
-            text += D_("You should ask your administrator to turn on HTTPS.")
-
-        return text + "</p><p>&nbsp;</p>"
-
-
-class SignalHandler(jsonrpc.JSONRPC):
-    def __init__(self, sat_host):
-        web_resource.Resource.__init__(self)
-        self.register = None
-        self.sat_host = sat_host
-        self._last_service_prof_disconnect = time.time()
-        self.signalDeferred = {}  # dict of deferred (key: profile, value: Deferred)
-        # which manages the long polling HTTP request with signals
-        self.queue = {}
-
-    def plugRegister(self, register):
-        self.register = register
-
-    def jsonrpc_getSignals(self):
-        """Keep the connection alive until a signal is received, then send it
-        @return: (signal, *signal_args)"""
-        _session = self.request.getSession()
-        profile = session_iface.ISATSession(_session).profile
-        if profile in self.queue:  # if we have signals to send in queue
-            if self.queue[profile]:
-                return self.queue[profile].pop(0)
-            else:
-                # the queue is empty, we delete the profile from queue
-                del self.queue[profile]
-        _session.lock()  # we don't want the session to expire as long as this
-                         # connection is active
-
-        def unlock(signal, profile):
-            _session.unlock()
-            try:
-                source_defer = self.signalDeferred[profile]
-                if source_defer.called and source_defer.result[0] == "disconnected":
-                    log.info(u"[%s] disconnected" % (profile,))
-                    try:
-                        _session.expire()
-                    except KeyError:
-                        #  FIXME: happen if session is ended using login page
-                        #        when pyjamas page is also launched
-                        log.warning(u"session is already expired")
-            except IndexError:
-                log.error("Deferred result should be a tuple with fonction name first")
-
-        self.signalDeferred[profile] = defer.Deferred()
-        self.request.notifyFinish().addBoth(unlock, profile)
-        return self.signalDeferred[profile]
-
-    def getGenericCb(self, function_name):
-        """Return a generic function which send all params to signalDeferred.callback
-        function must have profile as last argument"""
-
-        def genericCb(*args):
-            profile = args[-1]
-            if not profile in self.sat_host.prof_connected:
-                return
-            signal_data = (function_name, args[:-1])
-            try:
-                signal_callback = self.signalDeferred[profile].callback
-            except KeyError:
-                self.queue.setdefault(profile, []).append(signal_data)
-            else:
-                signal_callback(signal_data)
-                del self.signalDeferred[profile]
-
-        return genericCb
-
-    def actionNewHandler(self, action_data, action_id, security_limit, profile):
-        """actionNew handler
-
-        XXX: We need need a dedicated handler has actionNew use a security_limit
-            which must be managed
-        @param action_data(dict): see bridge documentation
-        @param action_id(unicode): identitifer of the action
-        @param security_limit(int): %(doc_security_limit)s
-        @param profile(unicode): %(doc_profile)s
-        """
-        if not profile in self.sat_host.prof_connected:
-            return
-        # FIXME: manage security limit in a dedicated method
-        #        raise an exception if it's not OK
-        #        and read value in sat.conf
-        if security_limit >= C.SECURITY_LIMIT:
-            log.debug(
-                u"Ignoring action  {action_id}, blocked by security limit".format(
-                    action_id=action_id
-                )
-            )
-            return
-        signal_data = ("actionNew", (action_data, action_id, security_limit))
-        try:
-            signal_callback = self.signalDeferred[profile].callback
-        except KeyError:
-            self.queue.setdefault(profile, []).append(signal_data)
-        else:
-            signal_callback(signal_data)
-            del self.signalDeferred[profile]
-
-    def connected(self, profile, jid_s):
-        """Connection is done.
-
-        @param profile (unicode): %(doc_profile)s
-        @param jid_s (unicode): the JID that we were assigned by the server, as the
-            resource might differ from the JID we asked for.
-        """
-        #  FIXME: _logged should not be called from here, check this code
-        #  FIXME: check if needed to connect with external jid
-        # jid_s is handled in QuickApp.connectionHandler already
-        # assert self.register  # register must be plugged
-        # request = self.sat_host.waiting_profiles.getRequest(profile)
-        # if request:
-        #     self.sat_host._logged(profile, request)
-
-    def disconnected(self, profile):
-        if profile == C.SERVICE_PROFILE:
-            # if service profile has been disconnected, we try to reconnect it
-            # if we can't we show error message
-            # and if we have 2 disconnection in a short time, we don't try to reconnect
-            # and display an error message
-            disconnect_delta = time.time() - self._last_service_prof_disconnect
-            if disconnect_delta < 15:
-                log.error(
-                    _(u"Service profile disconnected twice in a short time, please "
-                      u"check connection"))
-            else:
-                log.info(
-                    _(u"Service profile has been disconnected, but we need it! "
-                      u"Reconnecting it..."))
-                d = self.sat_host.bridgeCall(
-                    "connect", profile, self.sat_host.options["passphrase"], {}
-                )
-                d.addErrback(
-                    lambda failure_: log.error(_(
-                        u"Can't reconnect service profile, please check connection: "
-                        u"{reason}").format(reason=failure_)))
-            self._last_service_prof_disconnect = time.time()
-            return
-
-        if not profile in self.sat_host.prof_connected:
-            log.info(_(u"'disconnected' signal received for a not connected profile "
-                       u"({profile})").format(profile=profile))
-            return
-        self.sat_host.prof_connected.remove(profile)
-        if profile in self.signalDeferred:
-            self.signalDeferred[profile].callback(("disconnected",))
-            del self.signalDeferred[profile]
-        else:
-            if profile not in self.queue:
-                self.queue[profile] = []
-            self.queue[profile].append(("disconnected",))
-
-    def render(self, request):
-        """
-        Render method wich reject access if user is not identified
-        """
-        _session = request.getSession()
-        parsed = jsonrpclib.loads(request.content.read())
-        profile = session_iface.ISATSession(_session).profile
-        if not profile:
-            # FIXME: this method should not use _cbRender
-            #        but all txJsonRPC code will be removed in 0.8 in favor of webRTC
-            #        and it is currently used only with Libervia legacy app,
-            #        so we do a is_jsonp workaround for now
-            self.is_jsonp = False
-            # user is not identified, we return a jsonrpc fault
-            fault = jsonrpclib.Fault(
-                C.ERRNUM_LIBERVIA, C.NOT_ALLOWED
-            )  # FIXME: define some standard error codes for libervia
-            return jsonrpc.JSONRPC._cbRender(
-                self, fault, request, parsed.get("id"), parsed.get("jsonrpc")
-            )
-        self.request = request
-        return jsonrpc.JSONRPC.render(self, request)
-
-
-class UploadManager(web_resource.Resource):
-    """This class manage the upload of a file
-    It redirect the stream to SàT core backend"""
-
-    isLeaf = True
-    NAME = "path"  # name use by the FileUpload
-
-    def __init__(self, sat_host):
-        self.sat_host = sat_host
-        self.upload_dir = tempfile.mkdtemp()
-        self.sat_host.addCleanup(shutil.rmtree, self.upload_dir)
-
-    def getTmpDir(self):
-        return self.upload_dir
-
-    def _getFileName(self, request):
-        """Generate unique filename for a file"""
-        raise NotImplementedError
-
-    def _fileWritten(self, request, filepath):
-        """Called once the file is actually written on disk
-        @param request: HTTP request object
-        @param filepath: full filepath on the server
-        @return: a tuple with the name of the async bridge method
-        to be called followed by its arguments.
-        """
-        raise NotImplementedError
-
-    def render(self, request):
-        """
-        Render method with some hacks:
-           - if login is requested, try to login with form data
-           - except login, every method is jsonrpc
-           - user doesn't need to be authentified for getSessionMetadata, but must be
-             for all other methods
-        """
-        filename = self._getFileName(request)
-        filepath = os.path.join(self.upload_dir, filename)
-        # FIXME: the uploaded file is fully loaded in memory at form parsing time so far
-        #       (see twisted.web.http.Request.requestReceived). A custom requestReceived
-        #       should be written in the futur. In addition, it is not yet possible to
-        #       get progression informations (see
-        #       http://twistedmatrix.com/trac/ticket/288)
-
-        with open(filepath, "w") as f:
-            f.write(request.args[self.NAME][0])
-
-        def finish(d):
-            error = isinstance(d, Exception) or isinstance(d, failure.Failure)
-            request.write(C.UPLOAD_KO if error else C.UPLOAD_OK)
-            # TODO: would be great to re-use the original Exception class and message
-            # but it is lost in the middle of the backtrace and encapsulated within
-            # a DBusException instance --> extract the data from the backtrace?
-            request.finish()
-
-        d = JSONRPCMethodManager(self.sat_host).asyncBridgeCall(
-            *self._fileWritten(request, filepath)
-        )
-        d.addCallbacks(lambda d: finish(d), lambda failure: finish(failure))
-        return server.NOT_DONE_YET
-
-
-class UploadManagerRadioCol(UploadManager):
-    NAME = "song"
-
-    def _getFileName(self, request):
-        extension = os.path.splitext(request.args["filename"][0])[1]
-        return "%s%s" % (
-            str(uuid.uuid4()),
-            extension,
-        )  # XXX: chromium doesn't seem to play song without the .ogg extension, even
-           #      with audio/ogg mime-type
-
-    def _fileWritten(self, request, filepath):
-        """Called once the file is actually written on disk
-        @param request: HTTP request object
-        @param filepath: full filepath on the server
-        @return: a tuple with the name of the async bridge method
-        to be called followed by its arguments.
-        """
-        profile = session_iface.ISATSession(request.getSession()).profile
-        return ("radiocolSongAdded", request.args["referee"][0], filepath, profile)
-
-
-class UploadManagerAvatar(UploadManager):
-    NAME = "avatar_path"
-
-    def _getFileName(self, request):
-        return str(uuid.uuid4())
-
-    def _fileWritten(self, request, filepath):
-        """Called once the file is actually written on disk
-        @param request: HTTP request object
-        @param filepath: full filepath on the server
-        @return: a tuple with the name of the async bridge method
-        to be called followed by its arguments.
-        """
-        profile = session_iface.ISATSession(request.getSession()).profile
-        return ("setAvatar", filepath, profile)
-
-
 class Libervia(service.Service):
     debug = defer.Deferred.debug  # True if twistd/Libervia is launched in debug mode
 
@@ -1753,26 +618,18 @@
             self.base_url_ext = self.options.pop("base_url_ext")
             if self.base_url_ext[-1] != "/":
                 self.base_url_ext += "/"
-            self.base_url_ext_data = urlparse.urlsplit(self.base_url_ext)
+            self.base_url_ext_data = urllib.parse.urlsplit(self.base_url_ext)
         else:
             self.base_url_ext = None
             # we split empty string anyway so we can do things like
             # scheme = self.base_url_ext_data.scheme or 'https'
-            self.base_url_ext_data = urlparse.urlsplit("")
+            self.base_url_ext_data = urllib.parse.urlsplit("")
 
         if not self.options["port_https_ext"]:
             self.options["port_https_ext"] = self.options["port_https"]
-        if self.options["data_dir"] == DATA_DIR_DEFAULT:
-            coerceDataDir(
-                self.options["data_dir"]
-            )  # this is not done when using the default value
-
-        self.html_dir = os.path.join(self.options["data_dir"], C.HTML_DIR)
-        self.themes_dir = os.path.join(self.options["data_dir"], C.THEMES_DIR)
 
         self._cleanup = []
 
-        self.signal_handler = SignalHandler(self)
         self.sessions = {}  # key = session value = user
         self.prof_connected = set()  # Profiles connected
         self.ns_map = {}  # map of short name to namespaces
@@ -1820,15 +677,15 @@
         section = site_root_res.site_name.lower().strip()
         value = config.getConfig(self.main_conf, section, key, default=default)
         if value_type is not None:
-            if value_type == u'path':
+            if value_type == 'path':
                 v_filter = lambda v: os.path.abspath(os.path.expanduser(v))
             else:
-                raise ValueError(u"unknown value type {value_type}".format(
+                raise ValueError("unknown value type {value_type}".format(
                     value_type = value_type))
             if isinstance(value, list):
                 value = [v_filter(v) for v in value]
             elif isinstance(value, dict):
-                value = {k:v_filter(v) for k,v in value.items()}
+                value = {k:v_filter(v) for k,v in list(value.items())}
             elif value is not None:
                 value = v_filter(v)
         return value
@@ -1837,12 +694,12 @@
         self.ns_map = ns_map
 
     def _namespacesGetEb(self, failure_):
-        log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_))
+        log.error(_("Can't get namespaces map: {msg}").format(msg=failure_))
 
     @template.contextfilter
     def _front_url_filter(self, ctx, relative_url):
-        template_data = ctx[u'template_data']
-        return os.path.join(u'/', C.TPL_RESOURCE, template_data.site or u'sat',
+        template_data = ctx['template_data']
+        return os.path.join('/', C.TPL_RESOURCE, template_data.site or 'sat',
             C.TEMPLATE_TPL_DIR, template_data.theme, relative_url)
 
     def _moveFirstLevelToDict(self, options, key, keys_to_keep):
@@ -1860,41 +717,42 @@
         except KeyError:
             return
         if not isinstance(conf, dict):
-            options[key] = {u'': conf}
+            options[key] = {'': conf}
             return
-        default_dict = conf.get(u'', {})
+        default_dict = conf.get('', {})
         to_delete = []
-        for key, value in conf.iteritems():
+        for key, value in conf.items():
             if key not in keys_to_keep:
                 default_dict[key] = value
                 to_delete.append(key)
         for key in to_delete:
             del conf[key]
         if default_dict:
-            conf[u''] = default_dict
+            conf[''] = default_dict
 
     @defer.inlineCallbacks
     def backendReady(self, __):
-        if self.options[u'dev_mode']:
-            log.info(_(u"Developer mode activated"))
+        if self.options['dev_mode']:
+            log.info(_("Developer mode activated"))
         self.media_dir = self.bridge.getConfig("", "media_dir")
         self.local_dir = self.bridge.getConfig("", "local_dir")
         self.cache_root_dir = os.path.join(self.local_dir, C.CACHE_DIR)
         self.renderer = template.Renderer(self, self._front_url_filter)
-        sites_names = self.renderer.sites_paths.keys()
+        sites_names = list(self.renderer.sites_paths.keys())
 
         self._moveFirstLevelToDict(self.options, "url_redirections_dict", sites_names)
         self._moveFirstLevelToDict(self.options, "menu_json", sites_names)
-        if not u'' in self.options["menu_json"]:
-            self.options["menu_json"][u''] = C.DEFAULT_MENU
+        if not '' in self.options["menu_json"]:
+            self.options["menu_json"][''] = C.DEFAULT_MENU
 
         # we create virtual hosts and import Libervia pages into them
         self.vhost_root = vhost.NameVirtualHost()
         default_site_path = os.path.abspath(os.path.dirname(libervia.__file__))
         # self.sat_root is official Libervia site
+        root_path = os.path.join(default_site_path, C.TEMPLATE_STATIC_DIR)
         self.sat_root = default_root = LiberviaRootResource(
-            host=self, host_name=u'', site_name=u'', site_path=default_site_path,
-            path=self.html_dir)
+            host=self, host_name='', site_name='', site_path=default_site_path,
+            path=root_path)
         if self.options['dev_mode']:
             self.files_watcher.watchDir(
                 default_site_path, auto_add=True, recursive=True,
@@ -1906,19 +764,20 @@
         # FIXME: handle _setMenu in a more generic way, taking care of external sites
         self.sat_root._setMenu(self.options["menu_json"])
         self.vhost_root.default = default_root
-        existing_vhosts = {u'': default_root}
+        existing_vhosts = {b'': default_root}
 
-        for host_name, site_name in self.options["vhosts_dict"].iteritems():
+        for host_name, site_name in self.options["vhosts_dict"].items():
+            encoded_site_name = site_name.encode('utf-8')
             try:
                 site_path = self.renderer.sites_paths[site_name]
             except KeyError:
                 log.warning(_(
-                    u"host {host_name} link to non existing site {site_name}, ignoring "
-                    u"it").format(host_name=host_name, site_name=site_name))
+                    "host {host_name} link to non existing site {site_name}, ignoring "
+                    "it").format(host_name=host_name, site_name=site_name))
                 continue
-            if site_name in existing_vhosts:
+            if encoded_site_name in existing_vhosts:
                 # we have an alias host, we re-use existing resource
-                res = existing_vhosts[site_name]
+                res = existing_vhosts[encoded_site_name]
             else:
                 # for root path we first check if there is a global static dir
                 # if not, we use default template's static dic
@@ -1934,7 +793,7 @@
                     site_path=site_path,
                     path=root_path)
 
-                existing_vhosts[site_name] = res
+                existing_vhosts[encoded_site_name] = res
 
                 if self.options['dev_mode']:
                     self.files_watcher.watchDir(
@@ -1960,90 +819,22 @@
             self.vhost_root.addHost(host_name.encode('utf-8'), res)
 
         templates_res = web_resource.Resource()
-        self.putChildAll(C.TPL_RESOURCE, templates_res)
-        for site_name, site_path in self.renderer.sites_paths.iteritems():
-            templates_res.putChild(site_name or u'sat', ProtectedFile(site_path))
+        self.putChildAll(C.TPL_RESOURCE.encode('utf-8'), templates_res)
+        for site_name, site_path in self.renderer.sites_paths.items():
+            templates_res.putChild(site_name.encode('utf-8') or b'sat',
+                                   ProtectedFile(site_path))
 
-        _register = Register(self)
-        _upload_radiocol = UploadManagerRadioCol(self)
-        _upload_avatar = UploadManagerAvatar(self)
         d = self.bridgeCall("namespacesGet")
         d.addCallback(self._namespacesGetCb)
         d.addErrback(self._namespacesGetEb)
-        self.signal_handler.plugRegister(_register)
-        self.bridge.register_signal("connected", self.signal_handler.connected)
-        self.bridge.register_signal("disconnected", self.signal_handler.disconnected)
-        # core
-        for signal_name in [
-            "presenceUpdate",
-            "messageNew",
-            "subscribe",
-            "contactDeleted",
-            "newContact",
-            "entityDataUpdated",
-            "paramUpdate",
-        ]:
-            self.bridge.register_signal(
-                signal_name, self.signal_handler.getGenericCb(signal_name)
-            )
-        # XXX: actionNew is handled separately because the handler must manage
-        #      security_limit
-        self.bridge.register_signal("actionNew", self.signal_handler.actionNewHandler)
-        # plugins
-        for signal_name in [
-            "psEvent",
-            "mucRoomJoined",
-            "tarotGameStarted",
-            "tarotGameNew",
-            "tarotGameChooseContrat",
-            "tarotGameShowCards",
-            "tarotGameInvalidCards",
-            "tarotGameCardsPlayed",
-            "tarotGameYourTurn",
-            "tarotGameScore",
-            "tarotGamePlayers",
-            "radiocolStarted",
-            "radiocolPreload",
-            "radiocolPlay",
-            "radiocolNoUpload",
-            "radiocolUploadOk",
-            "radiocolSongRejected",
-            "radiocolPlayers",
-            "mucRoomLeft",
-            "mucRoomUserChangedNick",
-            "chatStateReceived",
-        ]:
-            self.bridge.register_signal(
-                signal_name, self.signal_handler.getGenericCb(signal_name), "plugin"
-            )
-
-        # JSON APIs
-        self.putChildSAT("json_signal_api", self.signal_handler)
-        self.putChildSAT("json_api", MethodHandler(self))
-        self.putChildSAT("register_api", _register)
-
-        # files upload
-        self.putChildSAT("upload_radiocol", _upload_radiocol)
-        self.putChildSAT("upload_avatar", _upload_avatar)
-
-        # static pages
-        # FIXME: legacy blog must be removed entirely in 0.8
-        try:
-            micro_blog = MicroBlog(self)
-        except Exception as e:
-            log.warning(u"Can't load legacy microblog, ignoring it: {reason}".format(
-                reason=e))
-        else:
-            self.putChildSAT("blog_legacy", micro_blog)
-            self.putChildSAT(C.THEMES_URL, ProtectedFile(self.themes_dir))
 
         # websocket
         if self.options["connection_type"] in ("https", "both"):
             wss = websockets.LiberviaPageWSProtocol.getResource(self, secure=True)
-            self.putChildAll("wss", wss)
+            self.putChildAll(b'wss', wss)
         if self.options["connection_type"] in ("http", "both"):
             ws = websockets.LiberviaPageWSProtocol.getResource(self, secure=False)
-            self.putChildAll("ws", ws)
+            self.putChildAll(b'ws', ws)
 
         ## following signal is needed for cache handling in Libervia pages
         self.bridge.register_signal(
@@ -2066,19 +857,10 @@
 
         # media dirs
         # FIXME: get rid of dirname and "/" in C.XXX_DIR
-        self.putChildAll(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir))
+        self.putChildAll(os.path.dirname(C.MEDIA_DIR).encode('utf-8'),
+                         ProtectedFile(self.media_dir))
         self.cache_resource = web_resource.NoResource()
-        self.putChildAll(C.CACHE_DIR, self.cache_resource)
-
-        # special
-        self.putChildSAT(
-            "radiocol",
-            ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"),
-        )  # FIXME: We cheat for PoC because we know we are on the same host, so we use
-           #        directly upload dir
-        # pyjamas tests, redirected only for dev versions
-        if self.version[-1] == "D":
-            self.putChildSAT("test", web_util.Redirect("/libervia_test.html"))
+        self.putChildAll(C.CACHE_DIR.encode('utf-8'), self.cache_resource)
 
         # redirections
         for root in self.roots:
@@ -2095,7 +877,7 @@
         self.site.sessionFactory = LiberviaSession
 
     def initEb(self, failure):
-        log.error(_(u"Init error: {msg}").format(msg=failure))
+        log.error(_("Init error: {msg}").format(msg=failure))
         reactor.stop()
         return failure
 
@@ -2109,9 +891,9 @@
 
     def _bridgeEb(self, failure_):
         if isinstance(failure_, BridgeExceptionNoService):
-            print(u"Can't connect to SàT backend, are you sure it's launched ?")
+            print("Can't connect to SàT backend, are you sure it's launched ?")
         else:
-            log.error(u"Can't connect to bridge: {}".format(failure))
+            log.error("Can't connect to bridge: {}".format(failure))
         sys.exit(1)
 
     @property
@@ -2128,7 +910,7 @@
             try:
                 return self._version_cache
             except AttributeError:
-                self._version_cache = u"{} ({})".format(
+                self._version_cache = "{} ({})".format(
                     version, utils.getRepositoryData(libervia)
                 )
                 return self._version_cache
@@ -2176,34 +958,30 @@
         session = request.getSession()
         sat_session = session_iface.ISATSession(session)
         if sat_session.profile:
-            log.error(_(u"/!\\ Session has already a profile, this should NEVER happen!"))
+            log.error(_("/!\\ Session has already a profile, this should NEVER happen!"))
             raise failure.Failure(exceptions.ConflictError("Already active"))
 
         sat_session.profile = profile
         self.prof_connected.add(profile)
         cache_dir = os.path.join(
-            self.cache_root_dir, u"profiles", regex.pathEscape(profile)
+            self.cache_root_dir, "profiles", regex.pathEscape(profile)
         )
         # FIXME: would be better to have a global /cache URL which redirect to
         #        profile's cache directory, without uuid
-        self.cache_resource.putChild(sat_session.uuid, ProtectedFile(cache_dir))
+        self.cache_resource.putChild(sat_session.uuid.encode('utf-8'),
+                                     ProtectedFile(cache_dir))
         log.debug(
-            _(u"profile cache resource added from {uuid} to {path}").format(
+            _("profile cache resource added from {uuid} to {path}").format(
                 uuid=sat_session.uuid, path=cache_dir
             )
         )
 
         def onExpire():
-            log.info(u"Session expired (profile={profile})".format(profile=profile))
-            self.cache_resource.delEntity(sat_session.uuid)
+            log.info("Session expired (profile={profile})".format(profile=profile))
+            self.cache_resource.delEntity(sat_session.uuid.encode('utf-8'))
             log.debug(
-                _(u"profile cache resource {uuid} deleted").format(uuid=sat_session.uuid)
+                _("profile cache resource {uuid} deleted").format(uuid=sat_session.uuid)
             )
-            try:
-                # We purge the queue
-                del self.signal_handler.queue[profile]
-            except KeyError:
-                pass
             # and now we disconnect the profile
             self.bridgeCall("disconnect", profile)
 
@@ -2276,11 +1054,11 @@
             ):  # try to create a new sat profile using the XMPP credentials
                 if not self.options["allow_registration"]:
                     log.warning(
-                        u"Trying to register JID account while registration is not "
-                        u"allowed")
+                        "Trying to register JID account while registration is not "
+                        "allowed")
                     raise failure.Failure(
                         exceptions.DataError(
-                            u"JID login while registration is not allowed"
+                            "JID login while registration is not allowed"
                         )
                     )
                 profile = login  # FIXME: what if there is a resource?
@@ -2307,8 +1085,8 @@
             if sat_session.profile != profile:
                 # it's a different profile, we need to disconnect it
                 log.warning(_(
-                    u"{new_profile} requested login, but {old_profile} was already "
-                    u"connected, disconnecting {old_profile}").format(
+                    "{new_profile} requested login, but {old_profile} was already "
+                    "connected, disconnecting {old_profile}").format(
                         old_profile=sat_session.profile, new_profile=profile))
                 self.purgeSession(request)
 
@@ -2323,23 +1101,23 @@
             fault = getattr(failure_, 'classname', None)
             self.waiting_profiles.purgeRequest(profile)
             if fault in ("PasswordError", "ProfileUnknownError"):
-                log.info(u"Profile {profile} doesn't exist or the submitted password is "
-                         u"wrong".format( profile=profile))
+                log.info("Profile {profile} doesn't exist or the submitted password is "
+                         "wrong".format( profile=profile))
                 raise failure.Failure(ValueError(C.PROFILE_AUTH_ERROR))
             elif fault == "SASLAuthError":
-                log.info(u"The XMPP password of profile {profile} is wrong"
+                log.info("The XMPP password of profile {profile} is wrong"
                     .format(profile=profile))
                 raise failure.Failure(ValueError(C.XMPP_AUTH_ERROR))
             elif fault == "NoReply":
-                log.info(_(u"Did not receive a reply (the timeout expired or the "
-                           u"connection is broken)"))
+                log.info(_("Did not receive a reply (the timeout expired or the "
+                           "connection is broken)"))
                 raise exceptions.TimeOutError
             elif fault is None:
-                log.info(_(u"Unexepected failure: {failure_}").format(failure_=failure))
+                log.info(_("Unexepected failure: {failure_}").format(failure_=failure))
                 raise failure_
             else:
-                log.error(u'Unmanaged fault class "{fault}" in errback for the '
-                          u'connection of profile {profile}'.format(
+                log.error('Unmanaged fault class "{fault}" in errback for the '
+                          'connection of profile {profile}'.format(
                               fault=fault, profile=profile))
                 raise failure.Failure(exceptions.InternalError(fault))
 
@@ -2353,14 +1131,14 @@
                     # existing session should have been ended above
                     # so this line should never be reached
                     log.error(_(
-                        u"session profile [{session_profile}] differs from login "
-                        u"profile [{profile}], this should not happen!")
+                        "session profile [{session_profile}] differs from login "
+                        "profile [{profile}], this should not happen!")
                             .format(session_profile=sat_session.profile, profile=profile))
                     raise exceptions.InternalError("profile mismatch")
                 defer.returnValue(C.SESSION_ACTIVE)
             log.info(
                 _(
-                    u"profile {profile} was already connected in backend".format(
+                    "profile {profile} was already connected in backend".format(
                         profile=profile
                     )
                 )
@@ -2386,10 +1164,10 @@
         """
         if not self.options["allow_registration"]:
             log.warning(
-                _(u"Registration received while it is not allowed, hack attempt?")
+                _("Registration received while it is not allowed, hack attempt?")
             )
             raise failure.Failure(
-                exceptions.PermissionError(u"Registration is not allowed on this server")
+                exceptions.PermissionError("Registration is not allowed on this server")
             )
 
         if (
@@ -2413,7 +1191,7 @@
                 return C.INTERNAL_ERROR
             else:
                 log.error(
-                    _(u"Unknown registering error status: {status}\n{traceback}").format(
+                    _("Unknown registering error status: {status}\n{traceback}").format(
                         status=status, traceback=failure_.value.message
                     )
                 )
@@ -2437,7 +1215,7 @@
         """Connect the profile for Libervia and start the HTTP(S) server(s)"""
 
         def eb(e):
-            log.error(_(u"Connection failed: %s") % e)
+            log.error(_("Connection failed: %s") % e)
             self.stop()
 
         def initOk(__):
@@ -2445,10 +1223,10 @@
                 connected = self.bridge.isConnected(C.SERVICE_PROFILE)
             except Exception as e:
                 # we don't want the traceback
-                msg = [l for l in unicode(e).split("\n") if l][-1]
+                msg = [l for l in str(e).split("\n") if l][-1]
                 log.error(
-                    u"Can't check service profile ({profile}), are you sure it exists ?"
-                    u"\n{error}".format(profile=C.SERVICE_PROFILE, error=msg))
+                    "Can't check service profile ({profile}), are you sure it exists ?"
+                    "\n{error}".format(profile=C.SERVICE_PROFILE, error=msg))
                 self.stop()
                 return
             if not connected:
@@ -2468,10 +1246,14 @@
 
     def putChildSAT(self, path, resource):
         """Add a child to the sat resource"""
+        if not isinstance(path, bytes):
+            raise ValueError("path must be specified in bytes")
         self.sat_root.putChild(path, resource)
 
     def putChildAll(self, path, resource):
         """Add a child to all vhost root resources"""
+        if not isinstance(path, bytes):
+            raise ValueError("path must be specified in bytes")
         # we wrap before calling putChild, to avoid having useless multiple instances
         # of the resource
         # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders)
@@ -2491,7 +1273,7 @@
             C.CACHE_DIR,
             C.LIBERVIA_CACHE,
             regex.pathEscape(site_name)]
-        build_path = u"/".join(build_path_elts)
+        build_path = "/".join(build_path_elts)
         return os.path.abspath(os.path.expanduser(build_path))
 
     def getExtBaseURLData(self, request):
@@ -2522,9 +1304,9 @@
                     proxy_netloc = proxy_host
                 else:
                     # if the proxy host has a port, we use it with server name
-                    proxy_port = urlparse.urlsplit(u"//{}".format(proxy_host)).port
+                    proxy_port = urllib.parse.urlsplit("//{}".format(proxy_host)).port
                     proxy_netloc = (
-                        u"{}:{}".format(proxy_server, proxy_port)
+                        "{}:{}".format(proxy_server, proxy_port)
                         if proxy_port is not None
                         else proxy_server
                     )
@@ -2540,10 +1322,10 @@
         else:
             proxy_scheme, proxy_netloc = None, None
 
-        return urlparse.SplitResult(
+        return urllib.parse.SplitResult(
             ext_data.scheme or proxy_scheme or url_path.scheme.decode("utf-8"),
             ext_data.netloc or proxy_netloc or url_path.netloc.decode("utf-8"),
-            ext_data.path or u"/",
+            ext_data.path or "/",
             "",
             "",
         )
@@ -2561,10 +1343,10 @@
         @return (unicode): external URL
         """
         split_result = self.getExtBaseURLData(request)
-        return urlparse.urlunsplit(
+        return urllib.parse.urlunsplit(
             (
-                split_result.scheme.decode("utf-8") if scheme is None else scheme,
-                split_result.netloc.decode("utf-8"),
+                split_result.scheme if scheme is None else scheme,
+                split_result.netloc,
                 os.path.join(split_result.path, path),
                 query,
                 fragment,
@@ -2579,13 +1361,13 @@
         @return (unicode): possibly redirected URL which should link to the same location
         """
         inv_redirections = vhost_root.inv_redirections
-        url_parts = url.strip(u"/").split(u"/")
-        for idx in xrange(len(url), 0, -1):
-            test_url = u"/" + u"/".join(url_parts[:idx])
+        url_parts = url.strip("/").split("/")
+        for idx in range(len(url), 0, -1):
+            test_url = "/" + "/".join(url_parts[:idx])
             if test_url in inv_redirections:
                 rem_url = url_parts[idx:]
                 return os.path.join(
-                    u"/", u"/".join([inv_redirections[test_url]] + rem_url)
+                    "/", "/".join([inv_redirections[test_url]] + rem_url)
                 )
         return url
 
@@ -2595,7 +1377,7 @@
         """helper method to purge a session during request handling"""
         session = request.session
         if session is not None:
-            log.debug(_(u"session purge"))
+            log.debug(_("session purge"))
             session.expire()
             # FIXME: not clean but it seems that it's the best way to reset
             #        session during request handling
@@ -2626,7 +1408,7 @@
         """
         sat_session = self.getSessionData(request, session_iface.ISATSession)
         if sat_session.profile is None:
-            raise exceptions.InternalError(u"profile must be set to use this method")
+            raise exceptions.InternalError("profile must be set to use this method")
         affiliation = sat_session.getAffiliation(service, node)
         if affiliation is not None:
             defer.returnValue(affiliation)
@@ -2641,12 +1423,12 @@
                         service=service, node=node, reason=e
                     )
                 )
-                affiliation = u""
+                affiliation = ""
             else:
                 try:
                     affiliation = affiliations[node]
                 except KeyError:
-                    affiliation = u""
+                    affiliation = ""
             sat_session.setAffiliation(service, node, affiliation)
             defer.returnValue(affiliation)
 
@@ -2655,9 +1437,9 @@
     def getWebsocketURL(self, request):
         base_url_split = self.getExtBaseURLData(request)
         if base_url_split.scheme.endswith("s"):
-            scheme = u"wss"
+            scheme = "wss"
         else:
-            scheme = u"ws"
+            scheme = "ws"
 
         return self.getExtBaseURL(request, path=scheme, scheme=scheme)
 
@@ -2672,7 +1454,7 @@
 
     def getHTTPDate(self, timestamp=None):
         now = time.gmtime(timestamp)
-        fmt_date = u"{day_name}, %d {month_name} %Y %H:%M:%S GMT".format(
+        fmt_date = "{day_name}, %d {month_name} %Y %H:%M:%S GMT".format(
             day_name=C.HTTP_DAYS[now.tm_wday], month_name=C.HTTP_MONTH[now.tm_mon - 1]
         )
         return time.strftime(fmt_date, now)
@@ -2685,7 +1467,7 @@
         Must be called only if TLS is activated
         """
         if not self.options["tls_certificate"]:
-            log.error(u"a TLS certificate is needed to activate HTTPS connection")
+            log.error("a TLS certificate is needed to activate HTTPS connection")
             self.quit(1)
         if not self.options["tls_private_key"]:
             self.options["tls_private_key"] = self.options["tls_certificate"]
@@ -2715,7 +1497,7 @@
                 )
                 buf = []
             elif not line:
-                log.debug(u"{} certificate(s) found".format(len(certificates)))
+                log.debug("{} certificate(s) found".format(len(certificates)))
                 return certificates
 
     def _loadPKey(self, f):
@@ -2739,7 +1521,7 @@
     def _getTLSContextFactory(self):
         """Load TLS certificate and build the context factory needed for listenSSL"""
         if ssl is None:
-            raise ImportError(u"Python module pyOpenSSL is not installed!")
+            raise ImportError("Python module pyOpenSSL is not installed!")
 
         cert_options = {}
 
@@ -2752,29 +1534,29 @@
             if not path:
                 assert option == "tls_chain"
                 continue
-            log.debug(u"loading {option} from {path}".format(option=option, path=path))
+            log.debug("loading {option} from {path}".format(option=option, path=path))
             try:
                 with open(path) as f:
                     cert_options[name] = method(f)
             except IOError as e:
                 log.error(
-                    u"Error while reading file {path} for option {option}: {error}".format(
+                    "Error while reading file {path} for option {option}: {error}".format(
                         path=path, option=option, error=e
                     )
                 )
                 self.quit(2)
             except OpenSSL.crypto.Error:
                 log.error(
-                    u"Error while parsing file {path} for option {option}, are you sure "
-                    u"it is a valid .pem file?".format( path=path, option=option))
+                    "Error while parsing file {path} for option {option}, are you sure "
+                    "it is a valid .pem file?".format( path=path, option=option))
                 if (
                     option == "tls_private_key"
                     and self.options["tls_certificate"] == path
                 ):
                     log.error(
-                        u"You are using the same file for private key and public "
-                        u"certificate, make sure that both a in {path} or use "
-                        u"--tls_private_key option".format(path=path))
+                        "You are using the same file for private key and public "
+                        "certificate, make sure that both a in {path} or use "
+                        "--tls_private_key option".format(path=path))
                 self.quit(2)
 
         return ssl.CertificateOptions(**cert_options)
@@ -2790,9 +1572,10 @@
         """
         # now that we have service profile connected, we add resource for its cache
         service_path = regex.pathEscape(C.SERVICE_PROFILE)
-        cache_dir = os.path.join(self.cache_root_dir, u"profiles", service_path)
-        self.cache_resource.putChild(service_path, ProtectedFile(cache_dir))
-        self.service_cache_url = u"/" + os.path.join(C.CACHE_DIR, service_path)
+        cache_dir = os.path.join(self.cache_root_dir, "profiles", service_path)
+        self.cache_resource.putChild(service_path.encode('utf-8'),
+                                     ProtectedFile(cache_dir))
+        self.service_cache_url = "/" + os.path.join(C.CACHE_DIR, service_path)
         session_iface.SATSession.service_cache_url = self.service_cache_url
 
         if self.options["connection_type"] in ("https", "both"):
@@ -2823,7 +1606,7 @@
         try:
             yield self.bridgeCall("disconnect", C.SERVICE_PROFILE)
         except Exception:
-            log.warning(u"Can't disconnect service profile")
+            log.warning("Can't disconnect service profile")
 
     def run(self):
         reactor.run()