Python3 Migrate

This commit is contained in:
MariuszC
2020-01-18 20:01:00 +01:00
parent ea05af2d15
commit 6cd7e0fe44
691 changed files with 201846 additions and 598 deletions

View File

@@ -0,0 +1,273 @@
import functools
import logging
import os
import urllib
import tornado.escape
import tornado.ioloop
import tornado.web
import tornado.websocket
import mopidy
from mopidy import core, models
from mopidy.internal import jsonrpc
logger = logging.getLogger(__name__)
def make_mopidy_app_factory(apps, statics):
def mopidy_app_factory(config, core):
if not config["http"]["csrf_protection"]:
logger.warning(
"HTTP Cross-Site Request Forgery protection is disabled"
)
allowed_origins = {
x.lower() for x in config["http"]["allowed_origins"] if x
}
return [
(
r"/ws/?",
WebSocketHandler,
{
"core": core,
"allowed_origins": allowed_origins,
"csrf_protection": config["http"]["csrf_protection"],
},
),
(
r"/rpc",
JsonRpcHandler,
{
"core": core,
"allowed_origins": allowed_origins,
"csrf_protection": config["http"]["csrf_protection"],
},
),
(
r"/(.+)",
StaticFileHandler,
{"path": os.path.join(os.path.dirname(__file__), "data")},
),
(r"/", ClientListHandler, {"apps": apps, "statics": statics}),
]
return mopidy_app_factory
def make_jsonrpc_wrapper(core_actor):
inspector = jsonrpc.JsonRpcInspector(
objects={
"core.get_uri_schemes": core.Core.get_uri_schemes,
"core.get_version": core.Core.get_version,
"core.history": core.HistoryController,
"core.library": core.LibraryController,
"core.mixer": core.MixerController,
"core.playback": core.PlaybackController,
"core.playlists": core.PlaylistsController,
"core.tracklist": core.TracklistController,
}
)
return jsonrpc.JsonRpcWrapper(
objects={
"core.describe": inspector.describe,
"core.get_uri_schemes": core_actor.get_uri_schemes,
"core.get_version": core_actor.get_version,
"core.history": core_actor.history,
"core.library": core_actor.library,
"core.mixer": core_actor.mixer,
"core.playback": core_actor.playback,
"core.playlists": core_actor.playlists,
"core.tracklist": core_actor.tracklist,
},
decoders=[models.model_json_decoder],
encoders=[models.ModelJSONEncoder],
)
def _send_broadcast(client, msg):
# We could check for client.ws_connection, but we don't really
# care why the broadcast failed, we just want the rest of them
# to succeed, so catch everything.
try:
client.write_message(msg)
except Exception as exc:
logger.debug(
f"Broadcast of WebSocket message to "
f"{client.request.remote_ip} failed: {exc}"
)
# TODO: should this do the same cleanup as the on_message code?
class WebSocketHandler(tornado.websocket.WebSocketHandler):
# XXX This set is shared by all WebSocketHandler objects. This isn't
# optimal, but there's currently no use case for having more than one of
# these anyway.
clients = set()
@classmethod
def broadcast(cls, msg, io_loop):
# This can be called from outside the Tornado ioloop, so we need to
# safely cross the thread boundary by adding a callback to the loop.
for client in cls.clients:
# One callback per client to keep time we hold up the loop short
io_loop.add_callback(
functools.partial(_send_broadcast, client, msg)
)
def initialize(self, core, allowed_origins, csrf_protection):
self.jsonrpc = make_jsonrpc_wrapper(core)
self.allowed_origins = allowed_origins
self.csrf_protection = csrf_protection
def open(self):
self.set_nodelay(True)
self.clients.add(self)
logger.debug("New WebSocket connection from %s", self.request.remote_ip)
def on_close(self):
self.clients.discard(self)
logger.debug(
"Closed WebSocket connection from %s", self.request.remote_ip
)
def on_message(self, message):
if not message:
return
logger.debug(
"Received WebSocket message from %s: %r",
self.request.remote_ip,
message,
)
try:
response = self.jsonrpc.handle_json(
tornado.escape.native_str(message)
)
if response and self.write_message(response):
logger.debug(
"Sent WebSocket message to %s: %r",
self.request.remote_ip,
response,
)
except Exception as exc:
logger.error(f"WebSocket request error: {exc}")
self.close()
def check_origin(self, origin):
if not self.csrf_protection:
return True
return check_origin(origin, self.request.headers, self.allowed_origins)
def set_mopidy_headers(request_handler):
request_handler.set_header("Cache-Control", "no-cache")
request_handler.set_header("X-Mopidy-Version", mopidy.__version__.encode())
def check_origin(origin, request_headers, allowed_origins):
if origin is None:
logger.warning("HTTP request denied for missing Origin header")
return False
allowed_origins.add(request_headers.get("Host"))
parsed_origin = urllib.parse.urlparse(origin).netloc.lower()
# Some frameworks (e.g. Apache Cordova) use local files. Requests from
# these files don't really have a sensible Origin so the browser sets the
# header to something like 'file://' or 'null'. This results here in an
# empty parsed_origin which we choose to allow.
if parsed_origin and parsed_origin not in allowed_origins:
logger.warning('HTTP request denied for Origin "%s"', origin)
return False
return True
class JsonRpcHandler(tornado.web.RequestHandler):
def initialize(self, core, allowed_origins, csrf_protection):
self.jsonrpc = make_jsonrpc_wrapper(core)
self.allowed_origins = allowed_origins
self.csrf_protection = csrf_protection
def head(self):
self.set_extra_headers()
self.finish()
def post(self):
if self.csrf_protection:
content_type = self.request.headers.get("Content-Type", "")
if content_type != "application/json":
self.set_status(415, "Content-Type must be application/json")
return
data = self.request.body
if not data:
return
logger.debug(
"Received RPC message from %s: %r", self.request.remote_ip, data
)
try:
self.set_extra_headers()
response = self.jsonrpc.handle_json(tornado.escape.native_str(data))
if response and self.write(response):
logger.debug(
"Sent RPC message to %s: %r",
self.request.remote_ip,
response,
)
except Exception as e:
logger.error("HTTP JSON-RPC request error: %s", e)
self.write_error(500)
def set_extra_headers(self):
set_mopidy_headers(self)
self.set_header("Accept", "application/json")
self.set_header("Content-Type", "application/json; utf-8")
def options(self):
if self.csrf_protection:
origin = self.request.headers.get("Origin")
if not check_origin(
origin, self.request.headers, self.allowed_origins
):
self.set_status(403, f"Access denied for origin {origin}")
return
self.set_header("Access-Control-Allow-Origin", f"{origin}")
self.set_header("Access-Control-Allow-Headers", "Content-Type")
self.set_status(204)
self.finish()
class ClientListHandler(tornado.web.RequestHandler):
def initialize(self, apps, statics):
self.apps = apps
self.statics = statics
def get_template_path(self):
return os.path.dirname(__file__)
def get(self):
set_mopidy_headers(self)
names = set()
for app in self.apps:
names.add(app["name"])
for static in self.statics:
names.add(static["name"])
names.discard("mopidy")
self.render("data/clients.html", apps=sorted(list(names)))
class StaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
set_mopidy_headers(self)
class AddSlashHandler(tornado.web.RequestHandler):
@tornado.web.addslash
def prepare(self):
return super().prepare()